A finance app I help maintain runs on three codebases: a Swift app for iOS, a Kotlin app for Android, and a Flutter rewrite that was meant to retire both. Eighteen months in, all three are still on the stores. That fact alone tells you most of what you need to know about cross-platform reality. The Flutter version did not replace the natives. It joined them.
The reason was not framework hate or stubborn iOS engineers. The reason was a long series of small details that added up. Scroll deceleration that felt slightly off on iOS. A keyboard that resigned half a frame too late. A platform view that flickered the first time it embedded a MapView. Each was solvable. Together they were a budget I did not plan for, and a perception problem I could not argue away with benchmarks.
This post is the unfiltered version of what I learned. I will not give you a single overall verdict, because there is not one. I will give you a verdict per platform and per concern, and a decision flowchart at the end.
Context: what a native developer needs to assume about Flutter
Flutter does not draw with UIView or android.view.View. It draws into a single platform surface using its own renderer (Impeller on iOS as of Flutter 3.10+, Skia on Android by default through 3.27, and Impeller now generally available on Android in newer releases). The widget tree you write in Dart is not translated into native widgets. It is composited by Flutter and handed to the GPU.
The practical consequence: anything Flutter has not reimplemented, you do not get for free. iOS scroll physics, accessibility traits, text input affordances, Material You dynamic color, predictive back animations on Android — these are either reimplemented inside Flutter, partially reimplemented, or available only through a platform view that re-introduces a native surface and its own perf cliffs.
If you are a UIKit or Jetpack Compose developer, this is the mental shift. You are not writing a thin wrapper. You are writing in a parallel UI universe that occasionally calls out to the host.
Rendering and frame budget
On a recent iPhone, both UIKit and Flutter hit 120 Hz on a ProMotion display without breaking a sweat for the kind of UI a finance app needs: lists, charts, tab transitions. The difference shows up at the edges.
UIKit reuses cell views aggressively and the OS gets to schedule layout work alongside other system rendering. Flutter rebuilds widgets but not necessarily render objects, and only repaints what is dirty. On a 60 Hz device, both feel fine. On a 120 Hz iPhone, Flutter occasionally drops a frame on the first scroll of a long, image-heavy list because image decode is happening on the UI isolate. The fix is precacheImage or moving decode work into a background isolate. The UIKit version simply does not exhibit this.
Gestures, scroll physics, and the part that ate my month
The hardest perception bug we shipped was the iOS scroll feel in Flutter. BouncingScrollPhysics is close to UIKit but not identical. The deceleration curve and rubber-band intensity differ in ways that a longtime iOS user notices without being able to name it.
Caption: the path a scroll gesture takes on UIKit versus Flutter on iOS. The Flutter path runs the simulation in Dart per frame, which is fine until the isolate is busy.
For text input, the gap is wider. A long press on a TextField in Flutter on iOS opens a selection toolbar that is visually similar but does not behave identically to UIKit's. Cursor positioning by drag, magnifier behavior, and keyboard-dismiss-on-scroll can all be tuned but require deliberate work.
Animation
Flutter's animation primitives are excellent. AnimationController, Tween, and Hero give you precise control without the ceremony of UIViewPropertyAnimator or Animator on Android. For implicit animations, AnimatedContainer, AnimatedSwitcher, and AnimatedOpacity are easier than the equivalents in either native SDK. This is the area where Flutter is the most pleasant to work in.
The Swift equivalent is more code and more concepts:
Platform feel
This is the squishiest category and the one where money is decided. By "platform feel" I mean: does the app obey the platform's idioms in the small details that the platform's own apps obey.
On iOS, Flutter struggles with: large title navigation transitions, swipe-back from any horizontal position, native context menus on long-press, share sheet animation timing, and the new iOS keyboard with its inline predictions. None of this is impossible. All of it is work the native version did not have to do.
On Android, Flutter does much better. Material 3 components in material package are credible. The dynamic color story works through ColorScheme.fromSeed and dynamic_color package. Predictive back is now supported in Flutter 3.27 with a PredictiveBackPageTransitionsBuilder, but the routing layer still needs to cooperate.
What I shipped: the verdict per platform
iOS: native Swift if the app must feel premium and your design system uses iOS-specific affordances. Flutter if the app is content-driven, you have one team, and you can absorb the perception tax.
Android: Flutter is a defensible default for new apps in 2025. Material 3 fidelity is good, performance on mid-tier devices is good, and the developer iteration loop is faster than Compose's.
Both at once: Flutter, only if you have honestly priced the platform-specific work that does not go away. The "one codebase" pitch is an asymptote you do not reach.
Decision flowchart
Caption: a real decision tree, not a marketing one. "Premium iOS feel" means the product has chosen iOS as its lead platform.
What I would do differently
- I would not pitch Flutter as "one codebase, two platforms" to leadership. That framing sets up disappointment when the iOS-specific budget reappears as bug tickets.
- I would have spent the first sprint building a small list-and-detail screen in all three stacks side by side on real devices, not a prototype. The deltas are invisible in screenshots.
- I would not have rewritten the native iOS app. I would have introduced Flutter as a module for new screens and let it earn its way in.
- I would have tracked frame timing with
flutter run --profile --trace-startupfrom week one, not after a regression report. - I would have hired one designer who actually owned the iOS feel, instead of relying on the same design system across platforms.
Closing opinion
Flutter is the right tool when you have a strong reason for it and an honest accounting of its costs. It is the wrong tool when you picked it because the executive summary said "save 50%". My direct recommendation: use Flutter for Android-led products and content apps, use native for iOS-led premium products, and stop pretending the choice is purely technical. For more on where Flutter on iOS specifically falls short, see Flutter on iOS still does not feel native. Here is exactly why. For the management-level version of the same call, see Should your team rewrite the native app in Flutter? The real answer.
Written by the author of Flutterstacks
A developer who shipped production apps in Swift, Kotlin, and Dart — with a genuine native reference point that most Flutter writers simply don't have.
More articles →