Limitations of Flutter add-to-app

Flutter add-to-app is officially supported and works well in production, but it comes with trade-offs that a standalone Flutter app doesn't have. Most of them have the same root: the native app stays in charge, and Flutter runs inside it as a guest (with its own engine, its own memory and its own toolchain). This page describes the limitations we have run into on real projects, what each one costs, and how we work around it.

Compared to a standalone Flutter app, you take on the following limitations:

  • Startup latency - the first Flutter screen has to spin up an engine, which a native screen never pays for.
  • Memory and app size - every engine has its own isolate and heap, and the Flutter runtime adds megabytes to the binary.
  • No shared Dart memory between screens - screens backed by different engines cannot see each other's state.
  • Slower developer experience - two toolchains, two debuggers, longer native builds.
  • Duplicated product infrastructure - design system, translations, analytics and crash reporting exist on both sides until the migration ends.

None of them is a blocker on its own. However, they add up, and they are the reason why we recommend add-to-app as a way to migrate rather than a place to stay.

Flutter engine startup latency in add-to-app

A native screen has no runtime to start. The first Flutter screen does - it needs an engine, which loads the AOT snapshot, starts the Dart isolate, runs your entrypoint and only then renders the first frame. If you create the engine when the user taps the button, they will see a blank or white screen for a moment, and in side-by-side demos that is the first thing stakeholders notice.

The standard fix is prewarming: create the engine early (e.g., on the splash screen, or on an appropriate screen in the app) so that it's ready by the time the user gets to Flutter. If you use Inlay, prewarming is taken care of for you. Inlay creates the engine group and prewarms the engine when the app starts, so the first Flutter screen doesn't wait for the engine, and the host app doesn't need any extra code for it.

Memory usage and app size impact in Flutter add-to-app

The first engine is the expensive one. Engines created from the same FlutterEngineGroup share the GPU context, font metrics and the isolate group snapshot, so according to the official Flutter docs each additional engine costs around 180 kB - as long as at least one engine from the group is alive. What engines don't share is the Dart heap: every engine runs its own copy of your startup code, caches and plugin instances.

The binary grows too. The Flutter documentation puts the engine's baseline at a few megabytes compressed; the rest is mostly your assets, fonts and packages. There is no fixed number for add-to-app, so measure it on your own app, with and without the module - and only on release builds, comparing store download sizes rather than upload packages.

No shared Dart memory between Flutter screens

If two Flutter screens can be alive at the same time with a native screen between them, you need two engines, and two engines mean two isolates that don't share memory. A singleton or an in-memory cache filled on one screen doesn't exist on the other. Developers coming from pure Flutter tend to find out from a bug report: the user logged in on one Flutter screen, and the next one still thinks they are logged out.

Any state shared between Flutter screens can live on the native side and be synchronized to every engine. Core concepts explain the mechanics, and Inlay's state management is our answer to it.

Developer experience: builds, hot reload and debugging

In add-to-app, Gradle and Xcode join the daily loop. Native builds get slower because they now build Dart too, native developers need the Flutter SDK on their machines, and debugging across the boundary means two debuggers at once, which is slow and sometimes flaky. Hot reload still works through flutter attach, but the general experience is not as seamless.

What helped us most is keeping the Flutter module runnable as a standalone app with fake native dependencies, so most UI work happens in the plain Flutter loop. End-to-end tests are another gap: Flutter's integration_test can't drive native screens, so a test that crosses the boundary needs a tool that can, such as Patrol.

Duplicated infrastructure in a hybrid native-Flutter app

This one is rarely mentioned in the beginning, but it's where a lot of the hidden cost sits. As long as the app is part native and part Flutter, several things exist twice:

  • The design system - implemented in Flutter and kept in line with the native version, or migrated screens look slightly off next to native ones.
  • Translations - native strings and ARB files need one source of truth, otherwise they drift apart within a few releases.
  • Analytics - screen names and event conventions have to match, or a migrated screen breaks the funnels the business relies on.
  • Crash reporting - Dart errors have to be reported explicitly and their symbols uploaded, otherwise they don't show up in your dashboard,
  • Theme and locale - when the user switches dark mode or language in native settings, every live Flutter engine has to follow.

None of this is technically hard, but all of it needs an owner. We recommend listing these items explicitly in the first weeks of the migration. This is where Converge pays for itself. Because it reads both codebases, it shows where the iOS and Android versions already differ before you add a third implementation, maps every screen and the navigation between them, and turns that into a wave-by-wave plan with an estimate per screen. You can re-run it as the migration goes and see how far you are from the exit, so the period of paying for two of everything is as short as it can be - and when it ends, this whole section stops applying.

Which add-to-app limitations matter for your app?

LimitationMatters most whenWhat we do about it
Startup latencyThe first Flutter screen is on a hot pathPrewarm the engine (Inlay helps you with that), measure on low-end devices
Memory and app sizeLow-end Android users, strict size budgetsEngine groups, release-build size checks in CI
No shared Dart memoryNative and Flutter screens are interleavedShared state on the native side, synchronized to every engine
Developer experienceNative teams new to FlutterStandalone module for daily work, cross-boundary E2E tests
Duplicated infrastructureLong migrationsOne source of truth per item, a named owner, a plan with an end date (Converge)

Most of these limitations shrink as more of the app moves to Flutter, and they disappear once Flutter takes over the shell. That's why we plan the exit strategy from day one.

Privacy Policy

On this page