Flutter in Production: What I've Learned from Using It to Develop an App with 1M+ Users

Xiaolong Li

• 5 min read
Flutter in Production: What I've Learned from Using It to Develop an App with 1M+ Users

Ever heard comments like this?
"Flutter is for small projects!"
"Flutter is a toy framework abandoned by Google!"

Well, I'm using Flutter to develop an app that serves over 1 million users — and I have some thoughts.


About the App

It's a B2C e-commerce app targeting the Japanese domestic market, focusing on a specific vertical. I joined the project from day zero as a senior developer, wrote about 30% of the production code (around 70,000 lines), and led the development of critical components like the API client, RBAC system, and overall feature-first architecture design.


Why Flutter?

There are many reasons to choose Flutter, but the biggest one for us was its cross-platform capability. In most cases, you don't have to worry about native APIs — which is a huge win for a small team trying to move fast.

That said, you can't completely avoid native development. Some dependencies — like ffmpeg (see this article about its deprecation) — are either outdated or poorly maintained. I had to dive into the native layer several times to patch things up.

If you're coming from Swift or Kotlin, I'd say Flutter is actually a great next step. Your native experience will definitely come in handy.


Architecture & Key Decisions

We adopted Clean Architecture, which has a learning curve but brings long-term benefits. In our Flutter implementation, we structured the app as follows:

  • Presentation Layer: Contains UI widgets and their corresponding ViewModels.
  • Application Layer: Includes use cases and logic that orchestrates domain rules.
  • Domain Layer: Defines core business entities and interfaces.
  • Data Layer: Handles API clients, local DB access, and repository implementations.

For state management, we chose Riverpod with code generation. It turned out to be a solid choice — it reduces the mental load of managing providers manually and scales well as your app grows.

Our CI/CD is powered by Fastlane (see this guide on Flutter + Fastlane). Because our Git provider doesn't support Apple Silicon-based builds very well, we trigger the pipeline manually from a local Mac. It's not perfect, but it works reliably.


What I've Learned (and What I'd Do Differently)

🔧 Mobile performance: Backend matters

One thing I learned the hard way: client-side rate limiting is not reliable.
If you expect traffic spikes (say, during campaigns or promotions), stay in sync with your backend team. Prepare for surge handling together.

🚀 Flutter-side optimizations

That said, there's still a lot you can do on the client side:

  • Image caching: I recommend using cached_network_image: ^3.4.1. But if your app is media-heavy, you should definitely look into using a CDN too.
  • Environment flavors: Always separate your environments — mock, staging, production. Absolutely make sure no one accidentally uploads a staging build to the App Store. Trust me, it happens.
  • TDD & code quality: These days, everyone's using AI tools and agents to write code. That's great — until it creates unmanageable tech debt.
    I adopted Test-Driven Development: writing unit tests and widget tests first, then building features around them. If it passes tests, it ships. Simple and safe.

Final Words

Flutter is not just for toy projects — we've proven it can power real, high-scale apps.

Feel free to reach out or explore more of my writing (and photography) at hokkaido.blue.

Xiaolong Li

Published on May 30, 2024