Taking an app from FlutterFlow (Low-Code) to Expo (React native) | (Part 1)

Taking an app from FlutterFlow (Low-Code) to Expo (React native) | (Part 1)

Intro

My favourite side project is "AI Nutrition Tracker". Available in the Apple App Store and on the web, it does, what the name suggests: it allows you to snap a picture of your dish and it will estimate ingredients, their weight, calories, proteins, carbs and fat.

I built it for myself as I always found apps like MyFitnessPal and the like to time-intensive. I don't want to spend more than 10 seconds per meal to track what I am eating. And if that means I am losing some accuracy, so be it.

It was built with mostly low-code and that has its costs. Both financially and in terms of what I can do with it and how far I can develop it. (I went into a lot of detail of why that is here)

It's been actively used by a few dozen people for the past year and I now want to use some free time to take it full code. This will be an interesting journey and it will take quite a few steps:

How to get to a full code app

These are roughly the steps I think I will encounter on the way. Each will likely be a part or multiple in the series.

  • Enough code to switch FlutterFlow to a free plan and save $70/month (this part)

  • Build a new and better AI model to scan meals
  • Turn off the paid plan of Buildship and save $25/month
  • Rebuild the application in Expo
  • Migrate the Firebase database to Supabase
  • Relaunch the whole application

Let's get started

As a freelance developer there will only be so many pockets of time during which I will be able to work on this. So the first thing I want to do is to move whatever is necessary to code so that I can basically turn off FlutterFlow. In practice I want to push my application to the App Store while I am still on the paid plan. It should already access API routes that I can change later on without having to push a new version to the App Store.

What code?

So far, my entire app is in FlutterFlow. I could use FlutterFlow's code export and carry on from there. Two problems:

  1. I do not want to learn Dart and Flutter. Mostly, because I am not sure if they will remain useful in the future and secondly, because I already know React and have been waiting for some time to try out Expo. (This will be a future blog post of itself).
  2. Code export quality. Even with the little Dart and Flutter I know, I can tell that the code you get out of FlutterFlow is, well, of questionable quality. It is verbose and it'll be easier to rewrite in any language than to refactor the code export.

Surprise

To be quick (remember busy freelancer live) I will actually start out in Next.js. I only need three API routes (image scan, text scan, and adjustment) and before I get into the weeds of Expo I can quickly create them in my best-known framework. Given that everything is in react, I will be able to simply copy&paste to Expo at a later point in time.

Why only three react routes? AI Nutrition Tracker is currently only accessing a few external APIs. The main one is Builship. Buildship is a visual backend builder. Neat idea, but in the end coding is faster. And I am not relying on a closed source partner.

Buildship Anatomy

Buildships in all its glory. (Image taken form their docs)

In Buildship I've got the main logic to scan images and text and to update the database with the results from the scan. All other APIs are either called through FlutterFlow directly (no, not best practice) or are triggered flows that also sit in Buildship.

My plan

My goal is to get rid of the FlutterFlow plan. That plan is mostly giving me easy deployment to the App Store. So in order to cancel it I need to deploy one last time and add all changes that are necessary. As it turns out all that is necessary is to replace the API URLs. Then I can change as much as I want in those routes without having to worry about another deployment in the future.

Replace the API routes

New Next.js project

Unsurprisingly, I created a new Next.js project. All that is then necessary is to create three new routes. One for the image scan, one for the text scan and one for the adjustment of a dish.

All that is necessary for now.

Set up for future changes

Each Buildship workflow can be accessed as an API call. Now, instead of directly calling it from FlutterFlow, I am calling my new routes from FlutterFlow and from therein I am calling the Buildship routes. To the user there will be no difference. But I've now got full power.

Because I can now use simple conditionals to decide what happens.

if (process.env.NODE_ENV === "production") {
const searchParams = request.nextUrl.searchParams;
const documentId = searchParams.get("documentId");
const user = searchParams.get("user");

if (!documentId || !user) {
return Response.json(
{ error: "Document ID and user are required" },
{ status: 400 },
);
}

try {
const buildshipHeaders = new Headers();
buildshipHeaders.append("Content-Type", "application/json");

const buildshipResponse = await fetch(`BUILDSHIP_URL`, {
method: "GET",
headers: buildshipHeaders,
redirect: "follow",
});

if (!buildshipResponse.ok) {
throw new Error(`Buildship API error: ${buildshipResponse.status}`);
}

return Response.json({ status: "success" });
} catch (error) {
console.error("Error calling Buildship API:", error);
return Response.json(
{ error: "Error processing buildship request" },
{ status: 500 },
);
}
}

And when I built the new AI logic (next blog post), I can simply plop that in. Done.

Best practices are there for a reason

Indeed, but only, because I built FlutterFlow in a mostly decoupled way to begin with (Phew...!). In practice that means not building these complex backend flows in FlutterFlow itself, but in Buildship. That is what makes it so that I only need to exchange a URL.

After deployment to the App Store and of the new Next.js app to Vercel, we are done with the first step. I just cancelled the FlutterFlow plan and everything is working like a charm.

Next up is is all about creating a new AI model for the food scanner.

Have the same problem?

If you have a large low-code app and are facing limitations in what can be achieved, let's talk. Be it technical questions or whether it makes sense to switch.