We Need To Talk About FlutterFlow

FlutterFlow is a popular low-code tool for building Flutter apps visually.

Its main features include a drag & drop UI, integration with external APIs and backends like Firebase and Supabase, configurable themes, built-in UI templates, and much more.

Just recently, FlutterFlow raised $25.5M in funding, aiming to increase its investment in AI and scale its enterprise efforts.

Moreover, the tool has been used by 1M users, and Upwork has dozens of job listings for FlutterFlow projects, so it appears there’s a big market for it.

So, is FlutterFlow the real deal? Or should you ignore it and continue building apps the old-fashioned way?

To find out, I decided to take it for a ride and share my learnings.

But before I continue, a disclaimer is in order.

FlutterFlow offers a ton of complex features and my knowledge of it is very superficial. During my research, I explored some of the pre-made template apps and tried to customize them, but I have not built any apps from scratch with FlutterFlow. So treat this article as an opinionated take from an experienced software engineer who’s skeptical about low-code tools.

With that said, here’s what we will cover:

  • First impression, official docs, testing the featured demos
  • The “Code View” feature
  • Tearing down the generated code
  • Firebase integration (it’s actually good!)
  • The frustrating experience of debugging FlutterFlow apps
  • Other random errors and bugs
  • Project history (a.k.a. subpar version control)
  • Customer support and community
  • FlutterFlow in production
  • Pros and cons
  • Who is FlutterFlow suitable for, and what can it do better?

Buckle up, for we’re going on an adventure! 👇

Update: since publishing the article, I've added additional sections about customer support, community, and running FlutterFlow in production.

FlutterFlow - First Impression

Before getting my hands dirty, I decided to check out the landing page, where I immediately found some bold claims:

  • Build applications faster than ever: Create beautiful UI, generate clean code, and deploy to the app stores or web in one click. Fully extensible with custom code.
  • No Code Ease, Pro Code Power: Easily build beautiful apps, connect data, and implement advanced functionality.
  • Customize Everything: There is no limit to what you can build with FlutterFlow.
  • Build Once, Deploy Everywhere: Don’t allow unclear expectations, short deadlines or limited budgets hold you back from building your dream product!

Having worked with other no-code tools before, I was particularly skeptical about “generate clean code”, “fully extensible”, and “there is no limit to what you can build”.

So, I decided to dig a bit deeper and look at the official docs.

FlutterFlow - Official Docs

Admittedly, the official docs seem quite comprehensive and include many annotated images and short video clips showing how all the various features work. Many useful guides are also included, and the FlutterFlow YouTube channel has a ton of video tutorials showing how to use the various features.

So far, everything seems encouraging, and I commend the FlutterFlow team for their efforts on this front.

Time to take it for a ride.

FlutterFlow Demos - Test Drive

The landing page contains three prominent examples of apps you can build with FlutterFlow:

Featured example apps on the FlutterFlow homepage
Featured example apps on the FlutterFlow homepage

These can be previewed without creating an account, so I picked the eCommerce template and decided to explore it:

Preview of the eCommerce app in FlutterFlow
Preview of the eCommerce app in FlutterFlow

My first impression was that the UI looks busy. Without a doubt, FlutterFlow is fully-featured, and if you want to get good at it, there’s a lot to learn.

Right off the bat, I decided to clone the project and run it:

When a FlutterFlow project is building, you'll see this screen
When a FlutterFlow project is building, you'll see this screen

Once the app started, I was happy to see that it was responsive and I could easily toggle between mobile, tablet, and desktop:

Example app running inside FlutterFlow, with toggles to choose between mobile, tablet, and desktop
Example app running inside FlutterFlow, with toggles to choose between mobile, tablet, and desktop

On the web demo, both Google and Apple sign-in failed (the login popup disappeared immediately), so I went ahead as a guest, and after that, I could browse the product catalog and see all the product details in the main app.

So far, all is good.

What About the Generated Code?

My next urge was to go to the developer menu and choose the View Code option:

The View Code option in the developer menu
The View Code option in the developer menu

Once clicked, this opens a new page where we can see all the pages and components (left sidebar), a code view (middle panel), and the corresponding UI (right panel):

Preview of the Code view
Preview of the Code view

I did like that it’s possible to click on any UI element in the preview panel and see the corresponding code in the middle, much like when using the widget inspector in the Flutter DevTools.

But to my shock and horror, I found out that the mainHomePage file contains 1941 lines of code (all for a single widget class), with the build method alone taking a whopping 1733 lines.

As I started reading the code, this meme came to mind:

Code Quality Measurement: WTFs/minute
Code Quality Measurement: WTFs/minute

If you want to see the generated code for yourself, use this link (note: you’ll have to create an account first).

Just for fun, I made a list of interesting things that are happening inside the build method:

  • L208: Call to setSystemUIOverlayStyle
  • L216: Call to context.watch<FFAppState>()
  • L263: onPressed callback with call to Navigator.pop(*context*)
  • L286: FutureProvider to fetch some data from the local _model
  • L413: onTap callback with call to setState which mutates the global FFAppState
  • L530: call to context.pushNamed to open the fullCartView page
  • L579: call to a global wrapWithModel function (more about this later)
  • L1006: onChanged callback with call to setState to mutate the local _model
  • L1163: onTap callback with call to context.pushNamed to open the productDetails page
  • L1189: StreamBuilder fetching the product list from the FFAppState
  • L1277: onPressed callback with call to setState which updates multiple properties in FFAppState and shows a snack bar
  • L1457: onPressed callback which (1) mutates some data in Firestore, (2) calls setState, (3) awaits for _model.waitForDocumentRequestCompleted(), (4) shows a snackbar
  • L1511: onPressed callback with logic very similar to the above
  • L1556: call to global responsiveVisibility function (not too sure what that is)
  • L1568: feeding a PagedListView a pagingController backed by a Firestore collection with a custom filter and order
  • L1850: onPressed callback with logic similar to L1277 and L1457

And as an extra fun fact, the most nested widget I could was more than 30 levels deep.

Note that this is for the mainHomePage alone. The productDetails widget also has nearly 1900 LOC, and the fullCartView has over 1000 LOC too.

Alright, alright, Andrea! What’s your point?

The point is that we have some big problems:

  • Performance: Gigantic widget classes with nested FutureBuilders and setState calls are bad for performance because they can cause big widget trees to rebuild far too often.
  • Separation of concerns: Business logic, data access, and UI code are all mixed up, making it hard to understand what’s going on.
  • Mutation of global state: According to the docs, the FFAppState class “acts as a central hub for managing the application's global state”. Yet, it is mutated in various places, each time causing a rebuild of every single FlutterFlow widget in the tree.

As it stands, the generated code is really hard to maintain.

Since this project is featured prominently on the home page, I wonder if this the best FlutterFlow can do?

Creating Custom Components

When it comes to Flutter best practices, it’s a good idea to refactor big build methods to separate widget classes.

As it turns out, FlutterFlow allows you to create and reuse custom components. In fact, it’s even possible to create a component from an existing widget.

And upon closer inspection, I noticed that this technique is indeed used to insert a TopNavWidget inside the MainHomePageWidget:

wrapWithModel( model: _model.topNavModel, updateCallback: () => setState(() {}), child: TopNavWidget(), )

So it seems that if we want to make things more manageable, creating custom components is the way to go, and FlutterFlow has a Widget Tree feature where we can inspect all the pages and components:

The sidebar shows a list of all the pages and components
The sidebar shows a list of all the pages and components

However, there are bigger problems on the horizon.

Downloading and Studying the Code

Since I was feeling quite limited by the built-in Code View, I decided to sign up for the free trial and download the project.

First off, I ran cloc to get an idea of how big is the example project:

cloc . --by-file 137 text files. 136 unique files. 1 file ignored. github.com/AlDanial/cloc v 1.98 T=0.09 s (1565.3 files/s, 451061.1 lines/s) ---------------------------------------------------------------------------------------------------------------------------- File blank comment code ---------------------------------------------------------------------------------------------------------------------------- ./flutter_flow/custom_icons.dart 3 1 2330 ./components/modal07_search_advanced/modal07_search_advanced_widget.dart 13 0 2162 ./pages/sub_pages/seller_details/seller_details_widget.dart 13 0 2019 ./pages/main_home_page/main_home_page_widget.dart 14 5 1942 ./pages/checkout_full_page/checkout_full_page_widget.dart 19 3 1922 ./pages/sub_pages/product_details/product_details_widget.dart 14 4 1880 ./pages/sub_pages/login_page/login_page_widget.dart 25 0 1696 ./pages/main_profile/main_profile_widget.dart 12 1 1410 ./pages/full_cart_view/full_cart_view_widget.dart 13 1 1183 ./archive/checkout_copy/checkout_copy_widget.dart 14 1 1064 ... many more files ... ---------------------------------------------------------------------------------------------------------------------------- SUM: 1761 625 36804 ----------------------------------------------------------------------------------------------------------------------------

At nearly 37K, this is a sizeable app, and many files contain over 1000 LOC.

After fixing an issue with a dependency out of date, I ran the flutter analyzer:

2500 issues found. (ran in 1.8s)

On average, that’s one rule violation for every 15 lines of code. 😱

I tried to fix the issues with dart fix --apply but didn’t get much luck:

Error from the analysis server: Exception while getting bulk fixes: Exception generating fix for prefer_const_constructors in /Users/andrea/work/codewithandrea/github/flutter-scratch/f_f_e_comm_template/lib/flutter_flow/flutter_flow_util.dart Bad state: Can't create both a generic file edit and a dart file edit for the same file

Directory Structure

Next up, I decided to take a look at the directory structure:

FlutterFlow directory structure
FlutterFlow directory structure

This is organized with a layer-first approach, as explained in the official docs.

I noticed that the pages and components always come as pairs with a widget and a model class:

Widgets and models are always arranged as pairs
Widgets and models are always arranged as pairs

Though, for large apps, I feel a feature-first approach would be better suited.

However, after taking a closer look at the code, I realized something.

FlutterFlow Decides Everything on Your Behalf

The eCommerce project I was testing had ~80 dependencies in the pubspec.yaml file and, for big projects, this is not unusual.

The problem is that FlutterFlow decides which packages (and versions) to use, and you have no control over that.

Likewise, state management is also baked-in using provider and a god-like FFAppState class, which is a scary-looking ChangeNotifier that looks like this:

Example FFAppState class for the eCommerce app example. The entire class is 284 lines long
Example FFAppState class for the eCommerce app example. The entire class is 284 lines long

As I pointed out earlier, every single widget calls context.watch<FFAppState>(); inside the build method:

// By design, every single FlutterFlow widget watches the global FFAppState variable @override Widget build(BuildContext context) { context.watch<FFAppState>(); // Listening to changes in App State ... }

As such, each widget gets rebuilt whenever notifyListeners() is called inside FFAppState, and that's not good.

For more details about how FlutterFlow manages state, read this page.

Does FlutterFlow Do Too Much?

Dependencies and state management are just two examples of things that FlutterFlow decides for you. And since mobile app development is a complex matter, it does a lot more, too:

When building mobile apps, UI is the tip of the iceberg
When building mobile apps, UI is the tip of the iceberg

As an app development tool, FlutterFlow decides everything on your behalf, and when you have to dig into the code, you have to live with those (questionable) decisions.

And from what I have seen, I would not wish to myself or others to maintain a codebase generated by FlutterFlow.

The solution?

Don’t Look at the Code

Maybe, as a FlutterFlow developer, this is the right philosophy.

Maybe, the point is that you should do all the work inside FlutterFlow, and never even look at the code.

But is that realistic?

FlutterFlow claims you can “build production ready apps” and that “there is no limit to what you can build”.

But when you need something that is not supported by the UI builder, you have to take things in your own hands and dive into the code.

And yes, FlutterFlow has a Custom Code feature that lets you add custom functions, widgets, actions and files. But that feels like a half-baked solution. What I’d really like is a proper solution where you can make changes in the IDE and sync them back with FlutterFlow.


With that said, there are a few areas where FlutterFlow deserves some praise. One of them is the wide range of supported integrations, including Firebase, Stripe, RevenueCat, and many others.

I’ve only tested Firebase, so I’ll limit my feedback to this. 👇

Testing the Firebase Integration

To test things out, I decided to create another project from one of the existing templates.

Creating a project from one of the available templates
Creating a project from one of the available templates

This time I went for Robin Do template, which is a simple TODO app.

As part of the initial setup, I was asked if I wanted to create a new Firebase Project or connect an existing one:

Firebase setup screen
Firebase setup screen

The “Create Project” option got stuck, but I was able to connect my Firebase project, and after following the recommended steps and a bit of troubleshooting (such as enabling various features in the Firebase console), I was quickly up and running:

Preview of the Robin Do template app
Preview of the Robin Do template app

As an experienced Firebase developer, I was pleased with how well the integration works.

However, once I started testing the app, I noticed some problems:

  • UI not rebuilding correctly after adding a task
  • Marking a task as complete didn’t work

And this leads me to the next point.

Debugging FlutterFlow Apps is Frustrating

When something doesn’t work in VSCode, I have a powerful debugger and a console at my disposal. I can set breakpoints, step through the Dart code, add print statements, and figure out how to fix things without going crazy.

But none of these tools are available in FlutterFlow. Instead, the default way to troubleshoot is to use the browser devtools:

Chrome devtools showing an uncaught error in main.dart.js
Chrome devtools showing an uncaught error in main.dart.js

Sadly, debugging cryptic errors inside main.dart.js is not for the faint of heart. Maybe a better alternative is to download the code and debugging it locally. But that would still be very time consuming since you won’t be familiar with the generated code.

Other Random Errors and Bugs

In my short-lived experience with FlutterFlow, I experienced a few more frustrations.

Some of the project templates did seem nice, but often had errors preventing me from running them:

Summary of the warnings and errors in one of the template apps
Summary of the warnings and errors in one of the template apps

At one point, I wanted to convert a widget into a custom component, but the app builder UI would just not work as intended.

Another time, I managed to do so, but my change broke the “Code View” page, and all I got was a message asking me to contact support.

These problems simply don’t exist when working with VSCode.

Next up, let’s talk about version control.

Project History

If you make a change that breaks the app, you may want to get back to a working state.

To address this, FlutterFlow has a Project History feature that lets you manually save a new version of your project. And for extra peace of mind, it also saves regular snapshots for you:

Project History window
Project History window

While this is somewhat useful, it’s not good enough for complex workflows.

On a positive note, users on the enterprise plan get access to a branching feature. This is designed for teams where collaborators need to work on separate features independently. And if you want, you can also manage your custom code in GitHub (I haven’t tested these features so I can't vouch for or against them).

This shows that FlutterFlow has thought about version control - though why pay for an enterprise plan to access features that are (likely) not as good as what GitHub offers for free?

Customer Support

Picture this: you’ve found a bug or a missing feature in FlutterFlow, and you can’t move forward. Or FlutterFlow changed some dependencies, and your app is no longer working.

Since you can’t fix this alone, you reach out to customer support and hope for the best.

Here’s an account from a fellow developer who went down this route:

Support is not usually very helpful and when there isn’t an easy answer the default behaviour is to lapse into silence. My worst experience of this was after having some time demonstrating a critical bug I got a response a month or so later asking how it was going. By this time I had changed backends and thrown away thousands of lines of custom code that I had written to deal with other (unfixed) bugs in FlutterFlow. Experiences like this are not only frustrating, but make using FlutterFlow a serious risk for any commercial project. Also, support only appears to be active during weekdays in US time. For a worldwide product and a tool which is pitched for professional use, this is not unacceptable.

And here’s another account from someone who also stopped using the product:

Flutterflow support - it is horrible! Their average response time for any request is at minimum is 72 hours jn my experience. It’s horribly slow and i dread having to contact them.

Community

I’ve read various comments stating that FlutterFlow is not doing enough to listen to its community. Here’s one example:

The community is not very busy in part because it is rare for anyone from the FlutterFlow team to participate and contribute. I read all the posts on the community daily. It does not take long because there are not that many. If a single FlutterFlow employee spent one hour a day reading and responding to community posts, it would go a long way towards making the community feel that there is more of a connection to the team.

If you want to get a real feel for this, you can check the community questions and browse the GitHub issues.

FlutterFlow in Production

So far, I’ve shown what the development and debugging experience feels like.

In the (unlikely) event that you don’t hit any roadblocks and manage to ship your app, what can you expect from running it in production?

Again, things are not looking good because you’re so dependent on FlutterFlow.

For a start, there is no support for separate dev/staging/prod environments, and you can’t revert to a previous version of FlutterFlow if an update breaks your app.

FlutterFlow Pros And Cons

After testing all the features above, I had a pretty good idea of the pros and cons.

Pros

  • Drag & Drop UI is nice, and so is the theming support
  • Very good documentation and tutorials
  • You can quickly build prototypes with it
  • Firebase Integration is pretty good (though I haven’t found a way to use it with the local emulator or configure multiple flavors)
  • Other useful features such as the widget palette, storyboard, and more.

Cons

  • Generated code is virtually unmaintainable (spaghetti code, god-like state class, linter rule violations, massive widgets, etc.)
  • FlutterFlow decides everything on your behalf (dependencies, state management, routing, data persistence, etc.)
  • You can’t really build production-ready apps (more on this below)
  • The project history feature offers a basic version control system, but it’s not as powerful as a complete GitHub based workflow
  • Poor debugging experience (you have to debug main.dart.js with the Chrome DevTools 😭)
  • Once you download and edit the code in your IDE, you can’t sync it back to FlutterFlow
  • Existing project templates are incomplete/not updated
  • Other random errors and bugs
  • Poor customer support and community management
  • No support for separate environments
  • FlutterFlow updates can break your app, and you can’t revert to an older version

Other things to consider:

  • Drag & drop UI is nice, but do you really need it when you already have excellent hot reload support in Flutter?
  • The time it takes to learn the FlutterFlow UI might be better spent learning the Flutter widget library.

When building complex apps, UI is not the hard part and there’s so much more we need to consider:

When building mobile apps, UI is the tip of the iceberg
When building mobile apps, UI is the tip of the iceberg

If you want control over these aspects, FlutterFlow is not the right tool for you.

Misleading Claims

Having listed the pros and cons, I’d like to debunk some of the claims made in the home page:

  • Generate clean code → No such thing. By default, the generated code produces massive widget trees and mutates global state, leading to poor performance, poor separation of concerns, bugs, and more. Of course, you can write code that is just as bad with an IDE.
  • Build Production Ready Apps → In my mind, “production ready” and “high-quality” sit pretty close together. Other terms I would use are robust, performant, and reliable. And currently, I don’t think FlutterFlow can deliver that.
  • There’s no limit to what you can build → There’s always a limit in what a UI tool can do, because it can never be as expressive and powerful as code. Sure, you can make the tool more complex to support more and more features, but that makes it harder to learn and use.
  • Fully extensible with custom code → Yes, it is extensible, but the “Custom Code” solution has some limitations and will never be as extensible as a “code-first” solution.
  • Easily build beautiful apps → That’s only true until you need to debug your code. Then, you can throw the “easily” out of the window and bash your head against the wall.

FlutterFlow feels like a tool that can get you 80% or 90% of the way there (for simple apps, that is).

But once you get to the last 10%, you’ve boxed yourself in a corner and you’re not going to have a good time.

Who is FlutterFlow Suitable For?

I think there’s a place for tools like FlutterFlow, and I can see the appeal for the no-code crowd. Thanks to all the pre-made templates and available integrations, you can indeed build simple apps, and Fireship even did a cool video showing what you can do with it.

If you're trying to find product-market-fit and don't care about the code, maybe FlutterFlow is for you. But professional software developers will feel very limited and will not want to maintain the code it generates.

So, if you use it, be mindful about its limitations and be prepared to restart from scratch if you’re serious about your project.

And if you have to choose between learning Flutter or FlutterFlow, go with Flutter.

What Can FlutterFlow Do Better?

I know that the team at FlutterFlow is working hard on their product, and I respect them for what they have accomplished to date.

As an experienced developer, here are my thoughts on what could be done better.

Debugging and Source Control

In my opinion, FlutterFlow shouldn’t reinvent the wheel when it comes to debugging and project versioning (as it stands, those are big pain points). Developers want to use the tools they know and love (Git, VSCode), and that’s only possible if they can easily transition from FlutterFlow to the IDE and back.

After all, FlutterFlow projects are Flutter projects and I’d like to have them stored locally on my system, and remotely on GitHub.

Code Generation

Simply put: I don’t think FlutterFlow should decide everything on my behalf.

I would be somewhat comfortable with a tool that generates the UI for me, but I want control over the rest:

When building mobile apps, UI is the tip of the iceberg
When building mobile apps, UI is the tip of the iceberg

Of course, my wish is the opposite of what a no-code tool does. But perhaps there’s a way to give power-users a choice, rather than deciding everything for them.

When it comes to code quality and correctness, I think this equation stands:

  • Experienced dev code > AI code > FlutterFlow code

If FlutterFlow wants to appeal to experienced developers, it needs to generate code that is as good as theirs, and AI could help bridge the gap.

Update The Landing Page

When it comes to marketing, over-promise and under-deliver is a sure-fire way to lose trust and get a bad reputation.

And to me, this seems exactly what’s going on with FlutterFlow. I hope the marketing team revisit all the bold claims and paint a more realistic picture of what the tool can offer.

Closing Thoughts

I decided to write this article after discovering various Reddit users who were expressing their frustration (some people even called it a predatory business).

While FlutterFlow has some happy customers, I don’t feel it’s the right tool for developers who want to build long-lasting, maintainable apps, and I hope that my overview of its limitations will help you make a more informed decision.

Happy coding!

Additional Resources

After publishing this article, I found some accounts from former FlutterFlow customers who shared their first-hand experience. Here are their insights:

Want More?

Invest in yourself with my high-quality Flutter courses.

Flutter In Production

Flutter In Production

Learn about flavors, environments, error monitoring, analytics, release management, CI/CD, and finally ship your Flutter apps to the stores. 🚀

Flutter Foundations Course

Flutter Foundations Course

Learn about State Management, App Architecture, Navigation, Testing, and much more by building a Flutter eCommerce app on iOS, Android, and web.

Flutter & Firebase Masterclass

Flutter & Firebase Masterclass

Learn about Firebase Auth, Cloud Firestore, Cloud Functions, Stripe payments, and much more by building a full-stack eCommerce app with Flutter & Firebase.

The Complete Dart Developer Guide

The Complete Dart Developer Guide

Learn Dart Programming in depth. Includes: basic to advanced topics, exercises, and projects. Last updated to Dart 2.15.

Flutter Animations Masterclass

Flutter Animations Masterclass

Master Flutter animations and build a completely custom habit tracking application.