FlutterFlow and the breaking point of No-code platforms

FlutterFlow and the breaking point of No-code platforms
Photo by Gary Bendig / Unsplash
⚠️
This post was written in a haze after 1 full week of non-stop coding & debugging. FlutterFlow may have addressed those issues at this point. However, I think the idea I presented here is still kinda valid. Take a look at Joel Spolsky's great essay on the problem of leaky abstraction for a much better presentation of my idea
No-code/Low-code enable Citizen Developer - people with little to no technical knowledge - to build applications in a fraction of time, with a fraction of cost.

- every No-code evangelist

I spent 1 week working non-stop, taking over a FlutterFlow application for one of my client. This client made the mistake of believing FlutterFlow's marketing team, embarked on a project that cost them at least a few thousands bucks for something that was supposed to be just a simple dumb client to an API. What they get in return is a monstrosity of a mobile app that has 3 different source of data with none of them in sync.

Hopefully you won't read this as a hate letter to FlutterFlow, I have nothing against them. Matter of fact, I think the FlutterFlow tech team is a group of very technically adept people, who are working to provide a very useful tool for everybody to make a mobile app. The non-technical world need a solution for the brain-melting tar-pit that is called mobile app development.

Sadly, FlutterFlow is now, in my very humble experience of working with 3 apps on the platform, a half-implemented visual abstraction of the Flutter framework and Dart language. And to be fair, this is really a critique for all no-code, low-code platforms on the market: You are abstracting the wrong thing!

Mouse-based expression builder

Please look at those two pictures. First one is how we can implement a moderately complex rendering condition for an element. Second one is how FlutterFlow translates this rendering condition to Dart code.

That's 8 levels of nested dialog holey fudge
All that translate to 8 lines of

This is a pretty extreme example, but it gives us a glimpse into how FlutterFlow requires us to build expressions. And don't even get me started on how annoying it is to write an expression that evaluate itself to something other than a bool, and then naturally you try to compare it to some value. But now you cannot just write == or >=, fuck no. You have to copy that expression you have just written, delete it, select a Comparison expression, paste the expression you have copied into the left-hand-side, select the comparison operator and then select the right-hand-side expression, which maybe another big old expression. And then, instead of writing && to combine two boolean expression, you create a Combine Condition node of type and that accept some children, which must be sub-expressions that evaluates to bool value.

The numbered CS-grads in my audience may start to recognize this thing from their Compiler 101 class.

Parse Tree
Sike, you have been AST-ed

For the uninitiated, what FlutterFlow takes you through is building an Abstract Syntax Tree (AST). This is the representation of how the compiler/interpreter understand the structure of a program, and then follow this tree to build executable binary code or interpret it.

But instead of executing this AST directly, FlutterFlow convert it to (debatably) clean generated Dart code, that the machine will have to reprocess into some kind of AST again before giving you something runnable on your phone.

So instead of abstracting away the process of writing code, FlutterFlow “de-abstract” that process into building something even lower level than code.

You may argue that by doing this, FlutterFlow can control what option is available to the builder when we select the next expression. Essentially, you can only continue a List expression with either a Get Item at Index, Get Number of Items, Filter List Item or Map List Item. Fair, but do you know what else also can do that? The Dart language grammar parser, some red underline and a very detailed error message.

Hey man are you trying to call zonk on a List<int>. That should be illegal.

The Dart grammar parser will know that a zonk token should not follow an expression that evaluate to a List<int>. Hook up the Intellisense dropdown, and you get your expression picker, for free.

Flowchart Action Builder

The other part of FlutterFlow that just doesn't sit right with me is the Action builder. Just build a 10-action long workflow, then you will see yourself pan your mouse at least 3 times to reach the final action. A branch's condition may not appear to you at the first glance.

What List?

And then the absolute baffling decision of implementing only a while-loop (this is reported in Aug. 2023, you may see the other types of loops when you read this article). Let me walk you through the process of writing a loop through a list fully on FlutterFlow:

  • Create a iterator new component state, because no, you cannot just create a variable in FlutterFlow. Everything has to be a state, either app state/component state.
  • Set iterator to some initial value at the start of the loop.
  • Set the while condition based on iterator
  • Remember to increment/decrement/update iterator inside the loop.

Do you remember what exact syntax that Dart give us to do this? Your lowly for loop, of course.

The same argument above applies for this case. FlutterFlow is again making developers build the abstract syntax tree for this action.

The breaking-point

A tool has its limit. A chainsaw is a good tool to split big chunks of wood quickly, but we cannot expect to use a chainsaw to do delicate cuts. We all know that doing big native features in Flutter might not even be a good idea, so it is only natural that we may feel the need to ditch FlutterFlow at some point when the scope gets big.

The problem? FlutterFlow (currently) set that point really low. Building & testing a moderately complex logic with FlutterFlow get frustrating quickly because of the cumbersome logic builder and the slow simulator. An iteration loop of a developer: write code - reload app - test app goes from merely 2 seconds with hot reload to at least 1 minutes, with no hot reload. We cannot seriously develop anything moderately complex with such poor development experience, so most developers decide to eject from FlutterFlow at this point and work on the code themselves.

Abstracting the wrong things

A no-code tool most powerful feature is how it enables us to build visual aspect of an application visually. This is something we can trade correctness against convenience, because UI is quite tangible, and it should be easy to fudge your app's appearance quickly, update this color here, move that section somewhere else. A visual builder for UI is the right evolution for application builder, and I see FlutterFlow being the answer for such a need. Not just non-technical people, but developers also benefit from a visual UI builder.

But from the given examples above, we see FlutterFlow also tries to also abstract out the logical building part of the Developer Experience, which I would say is a well-intentioned but an ill-thought-out approach. The reason is: logic is mathematic, and us human are actually pretty well-trained to understand the rather abstract-but-succint expressions based on symbols. Don't believe me? Just do a little test, which representation below do you have less difficulty understanding and/or expressing.

$$\frac{a + b}{c * d + e}$$

My key takeaway here: Visualization for logic should only serve to accompany and possibly help to understand complex logic, not to actually build it.

What I would suggest if I am a product person on FlutterFlow

I would raise a few points to the FlutterFlow team:

  • Improve the editor to make code-editing a first class citizen in FlutterFlow. Which mean allowing use to switch to writing a lot of code for most logic & actions.
  • Predefined actions should be provided as functions. For example, wrap the change page actions in a well-defined function, like the following pseudo-Flutter code below.
enum Page {
  Homepage,
  Dashboard,
}
Future<void> changePage(BuildContext context, Page page) {
  context.push(Homepage.value);
}

This way, you can both build the app through the drag & drop route, and can still switch back & forth between the Low-code UI and the Tradition Method of Writing Code.

Final words?

Exactly what you run from, you end up chasing.
- Jerrod Carmichael on Tyler, The Creator's album Igor

In the end, what you build to try abstracting out the complexity of a programming language is still just another programming language, albeit with a much more unwieldy syntax.