Flutter is ideal when you want a consistent experience across platforms. The more platforms you target, the more value you’ll derive from Flutter. That value derives, not only from consistency in behavior and presentation, but also from consistency in implementation: unlike React Native, Flutter explicitly aims to deliver the same experience on multiple platforms using a single codebase:
Our goal for this year  is that you should be able to run flutter create: fluttler run and have your application run on Web browsers, macOS, Windows, Android, Fuchsia, and iOS, with support for hot reload, plugins, testing, and release mode builds. We intend to ensure that our Material Design widget library works well on all these platforms. (The Flutter Roadmap)
Flutter guarantees consistency by owning the entire user experience, rather than deferring to per-platform UI toolkit components. Like a game engine, it takes control of both drawing and event handling and handles them, both input and output, itself. This makes a marked contrast from React Native, which instead marshals native platform views into rendering and event handling on its behalf. This enables Flutter to reliably render content without dropping any frames and with every pixel under its control. A wide array of widgets is available, and their behavior will change when you update your app, not in response to platform changes. This gives you great control, at one main cost: apps using Flutter do not automatically update to track changes in system styles and behaviors. You can adopt Material Design to mitigate the impact of that caveat because Material Design apps follow that UI standard, rather than any specific platform’s.
This is an intentional tradeoff: Flutter’s bet is that the future is more consistently-branded experiences across platforms, where that consistency is owed first to the brand, secondly, if at all, to the platform. Its vision is “to provide a portable toolkit for building stunning experiences wherever you might want to paint pixels on the screen” (“Announcing Flutter 1.20” for one example, though restatements of this vision are many).
Flutter’s community seems small next to React Native, but large next to Multiplatform Kotlin. Its community is certainly very vocal and visible; blog posts, conferences, library updates, and other events and publications continue to stream out. Its “own the stack” approach does more to guarantee consistency across platforms than React Native can provide, and unlike Multiplatform Kotlin, it can readily share UI code across platforms. Also unlike the situation with Multiplatform Kotlin vs Kotlin/JVM, most Dart libraries also work with Flutter, so you won’t find yourself stuck using less-tested packages for common needs. Its hybrid compilation approach and framework design give developers rapid build-and-deploy with stateful hot-reload during build and test while guaranteeing end users fast apps with a consistent latency in release builds. (This consistency results from using ahead-of-time compilation without runtime JIT compilation. Using AOT compilation to native binary code speeds code loading because the code has already been processed for easy loading and running. Not using JIT avoids variation in performance and latency, because there is no JIT compiler variously optimizing and de-optimizing various codepaths based on the specific details of what code has been run when since app launch.)
Accessibility support is solid
I worried that custom rendering would lead to broken accessibility support. In fact, its accessibility support is solid: it builds and maintains a “Semantics tree” to represent accessibility elements as a core part of its rendering pipeline. There’s even automated test support for checking some standard accessibility guidelines, such as text contrast. Dynamic Type support is baked into the Flutter framework. I have not had a chance to investigate how well the stock UI components respect accessibility preferences like Reduce Motion or Bold Text, but those preferences are readily accessible, so it would be easy to accommodate them yourself.
Localization support is not bad
I also worried about Flutter’s localization support, because localization is often overlooked. But the Dart
Intl package has robust i18n support, including handling plurals and gender in building localized strings. Number formatting is rather complete. Time support is weak, in that time zones beyond UTC and Local are not supported, and calendar math (nor support for non-Gregorian calendars) is not provided. Overall, it’s a judicious subset of ICU. It’s not as automatic or comprehensive as iOS’s localization system, which also handles resolving localized non-string assets, and automatically selects and loads the appropriate locale configuration on your behalf, but all the pieces are there. And the community is filling gaps; for example, timezone delivers a zone-aware
DateTime, while buddhist_datetime_dateformat handles formatting dates per the Buddhist calendar.
Codesharing is nigh total, including UI code
Code can be readily shared across platforms, including UI code. Accommodating platform differences, such as by varying the core information architecture, is not any more difficult than an if/else. You can get yourself into trouble with plugins, which are packages with platform-specific native code, but Flutter’s federated plugins approach serves to make clear which platforms are supported, and even to allow third-parties to provide support for additional platforms. This means that if you hit on a plugin that could be supported on a platform you need but isn’t yet, you could readily implement and publish the support for the plugin on that platform.
Platform support favors Material Design on mobile OSs
“Across platforms” primarily means “across iOS 8+ and Android 4.1 Jellybean and later (API level 16+)”: As of July 2020, Flutter Web is in beta, Flutter macOS is in alpha, and Flutter Linux and Windows are pre-alpha. That said, Flutter’s stated aim is to become “a portable UI framework for all screens”, and it is making steady progress towards that aim. The Flutter team is making visible progress in public with clear goals and rationale. And I was impressed at the team’s response time to third-party contributions: I had several documentation PRs merged in the week I spent with Flutter.
Unlike React Native, which often has had an iOS-first bias, Flutter’s bias is towards Android, or rather, the Android design language. The Material Design widgets are available and work consistently across platforms; iOS is not stinted there. But documentation and examples for using the Cupertino widget set that reproduces the iOS look and feel is harder to come by, and I had trouble getting it to play nice with Dark Mode. If you’re going full-on branded and effectively building your own widget set, you’re going to be on even ground across both platforms, and it might even prove easier than using the first-party toolkit for those platforms.
Dart is an effective tool
I didn’t worry about the Dart language, which is used in writing Flutter apps. It’s thoroughly inoffensive, it has some nice touches, and the ecosystem features a solid static analysis, testing, and packaging story. But if you’re coming from Swift, Kotlin, or TypeScript, Dart will feel familiar, and you’ll be productive very quickly. And if you’re coming from Swift, you’ll be pleased to find async/await support. The biggest tripping points I ran into were:
- Dart requires you write semicolons.
- Dart uses C-style SomeType thing rather than Pascal-style thing: SomeType type declarations.
- Dart uses final/const vs var rather than let/var or val/var. (The final/const distinction resembles const vs constexpr in C++.)
- The cascade syntax with . . is hard to look up unless you happen to guess it’s a descendant of the Smalltalk syntax of a similar name that was used similarly. That said, it reads naturally enough that it’s not much of a stumbling block.
Flutter seems like the best tool to date for delivering the “same app” across all platforms. It’s a young tool with some rough edges, but I have yet to encounter a multi-platform tool that won’t cut you at times; Flutter’s gotchas seem mostly peripheral, and its rendering approach guarantees a consistency that’s hard to come by otherwise.
The team behind Flutter is responsive and has a clear vision for where the framework is going, including a public roadmap. The actual work happens in public on GitHub, not in a private tree, so it’s easy to follow.
Flutter is both easy to work with and easy to contribute to. The community and project are well-organized, and you probably won’t spend a lot of time flailing around for a library thanks to the Flutter Favorites program.
If you need someone to build an app for multiple platforms, give Big Nerd Ranch a ring.