•Published: Sep 14, 2021
Over the years, it’s become common for developers to compare Flutter and React Native. Both are prevalent multi-platform tools for quickly and easily building mobile applications.
If you walk into a developer meetup and ask the question, "What should I pick, Flutter or React Native?", you’re setting the stage for a very spirited debate.
In this blog post, let's try to answer this question fairly and respectfully. In the end, the choice of platform is only one of many factors in the success of applications.
In this post, we’ll cover the following topics:
- Developer Experience
- 3rd Party Packages
- Developer Productivity
- The Future
Before we get started, it’s important to mention that by day, I’m a Flutter Developer. Although this blog post has been reviewed by both our Flutter and React Native engineering teams, I think it’s important to clarify 🙂
It’s also important to mention that at Stream, we have experience using both frameworks. In fact, we’ve dedicated teams for each Chat Messaging SDK. This benefits the greater developer community in that teams considering Stream aren’t forced to adopt a framework they don’t want to use in order to provide their end-users with a seamless chat experience.
That said, let's discuss how these frameworks differ and how they each appeal to different teams, devs, and use cases.
“Developer experience” is the general feeling experienced developers and users get when using a library. Everything from the documentation to the actual API surface of that library can influence the developer experience, which often makes it a top consideration for teams when deciding on a new SDK for an application.
In the case of Flutter, it’s very easy for developers and companies to look at quantitative data to gauge the community's general feeling and satisfaction towards the SDK: The team at Google sends quarterly surveys to Flutter developers, then publishes an extensive report detailing their findings.
One stat that stands out from the latest Q2 Flutter report is the library’s satisfaction rate: At the end of Q2, Flutter boasted a 92% satisfaction rate among its user base.
The satisfaction rate can be attributed to a few key things Flutter does well:
API documentation and samples
The Developer Relations team and Flutter community have done an outstanding job documenting the framework’s APIs and common patterns. Their recent campaign to encourage developers to "adopt" a widget not only opened the doors to external contributions but also resulted in dozens of code samples for common Flutter widgets.
Developer tooling is a core part of the Flutter experience. The framework has dedicated itself to building and investing in developer tools. Flutter's DevTools is an interactive suite of performance and debugging tools that allow developers to interact directly with their IDE from Chrome. Using Flutter DevTools, developers can easily debug common layout problems, interactively tweak layouts, and much more.
3rd party packages
Flutter and Dart also have a package management system named Pub. On Pub, developers can upload, browse, and use packages created by the community. Packages on Pub can range from Flutter widgets to pure Dart libraries such as http and crypto. The Flutter team together with the community reviews popular packages under the "Flutter Favorites" label. This makes it easy for new developers to quickly identify high-quality (and trusted) libraries for their projects.
In terms of developer experience, React Native is similar to Flutter in some ways. The team at Facebook has done a great job laying out information on React Native's getting started page. On this page, developers are taken on a guided tour around the framework.
While I couldn’t find any official user studies for React Native, it’s worth noting that React Native has been around for much longer than Flutter. As such, it has a considerable community following compared to Flutter.
The number of packages and plugins for React Native vastly outnumbers that of Flutter since developers can use most JS libraries and React packages.
With regards to tooling, React Native developers have a few more options than their Flutter counterparts. Given the maturity of the framework and its ability to leverage JS toolchains, React Native developers can choose from a significant number of stock JS tools to improve their workflow. Some of these include IDEs such as VSCode to lint packages like ESLint.
Flutter's purpose-built DevTools does help set the framework apart from React Native. It allows developers to quickly debug very complex layout, performance, and network problems without installing additional tooling. In addition to standard React Developer Tools, React Native now has support for Flipper, an extensible mobile app debugger for React Native applications.
Flipper's debugger for React Native: https://fbflipper.com/
If you want to learn more about React Native tools, check out Instabug’s post covering the topic.
In summary, the developer experience for both platforms is what you would expect for a modern development framework in 2021. They have excellent documentation (Flutter has a slight advantage here) with detailed guides and developer tools.
Developer productivity is where things really get interesting. Let's preface this section by stating some of the points here are entirely up to personal preference. Every developer is different, and each prefers their own workflow and tools. If you have thoughts leaning one way or another, do (respectfully) tell us about it 🙂. Feedback is always helpful.
When looking at developer productivity, we’ll examine each framework through the following lens:
- Language choice
- Developer ecosystem (packages and plugins)
Let's start with possibly the most debated topic of all: language choice. The choice of language has a ripple effect throughout the entire framework. In addition to developer productivity, the choice in language directly impacts the framework's performance—something we will discuss in-depth later on.
For the most part, the structure of your React Native code strongly resembles that of your React code. In fact, it may even be possible to reuse logic from your React projects in your React Native projects.
While using JS allows developers to leverage the vast number of projects and libraries built by developers for the web in their applications, it also has some serious drawbacks:
For one, unless you opt into using Typescript (which only adds static typing), JS is a dynamic language. This means there’s no sound type system or null-safety present in the logic.
If you’ve worked in native application development and you’re evaluating React Native as your first cross-platform framework, the lack of strong types and analyzer warnings might shock you at first. While tools such as ESLint help reduce the number of obvious bugs in your code, it’s still possible to run into situations where something unexpected happens that’s only detected at runtime.
Since JS can’t compile to native machine code, all code written as part of your React Native application needs to be compiled on the user’s device with a JS virtual machine.
I’m not saying dynamic languages are terrible. Some developers love using JS for its flexibility and low barrier to entry. However, it’s important to note that when working on larger applications, you’ll be prone to errors that are exceedingly rare in the native or strongly typed world 🙂
Flutter as a framework is in a unique position. Dart, the language used by Flutter to write the majority of the stack, is also maintained by Google and the Flutter team. (As a fun fact, both the Dart and Flutter teams share the same Group Product Manager 😉.)
This means the team can optimize the framework (Flutter) and the language (Dart) to help improve the developer experience. A perfect example of this is the recent addition to condition literals and the spread operator. Both are small additions to the language that go a long way in making it more useful to developers building applications with Flutter.
In terms of the language itself, Dart can be described as having a c-style syntax that should feel very familiar to developers who have used JS, C#, or Java in the past.
Unlike JS, Dart is statically typed and was recently updated to have sound-null-safety. Modern Dart code is now non-nullable by default, meaning the compiler can warn you about potential null-dereference errors before they occur.
It’s also worth mentioning that Dart can compile using both Just-in-time (JIT) and Ahead-of-Time (AOT) modes, meaning developers can benefit from a fast iterative dev cycle using JIT but produce a small and performant application compiled with AOT for end users. The language can also compile to native machine code on both desktop and mobile devices, so there’s no need for a virtual machine in production applications.
While Flutter doesn’t have a large collection of open-source packages at its disposal like JS, it makes up for this in performance. A well-written Flutter application can easily run at 60–120fps in production.
In addition, developers can offload large computational tasks, such as JSON decoding or sorting, to background isolates (Flutter/Dart's version of threads) to help maintain performance and avoid blocking the UI.
A key selling point of any SDK or framework is the tooling and ecosystem around it. Both SDKs have a very large and passionate developer ecosystem that contributes to open source.
React Native has a large benefit since developers can leverage most open-source JS libraries in their applications. As a result, developers can go on to their favorite package manager (npmjs comes to mind), search for a JS package that solves their use case, and easily add it to their application.
Flutter, on the other hand, has a healthy open-source presence, but it pales in comparison to the sheer number of packages React Native developers can choose from.
For example, according to libraries.io, the total number of packages on Pub currently sits at 24,952, while the number of packages on npm sits at 1.98M.
In the case of both platforms, it’s not uncommon to find a wide range of projects on NPM and Pub.dev. To help developers pick the right package for their project and give an additional level of confidence, the Flutter team has started a Flutter Favorites program, which reviews commonly used packages.
While NPM is the most popular source for JS packages, it’s worth noting that not all packages on NPM support React Native. React Native Directory, a website dedicated to hosting React Native libraries, currently lists 982 libraries.
To state the obvious, there’s always been a noticeable performance gap between Flutter and React Native. Over the years, that performance gap has narrowed. Today, while the gap isn’t as large as it used to be, Flutter still has a slight edge on React Native, but for most applications, it’s negligible.
While the advertising for both frameworks would have you believe they are both "truly native" applications, this isn’t necessarily true.
React Native Performance
In the case of React Native, the UI is made of native components from Android and iOS. This means that you’re using the platform widgets that ship with each version of the operating system.
When a developer uses a Text to display information in their application, under the hood React Native displays a
TextView on Android and a
UITextView on iOS. This approach certainly has its upsides since the UI is always familiar to the end-user and native for each device.
Code and business logic, on the other hand, are written in JS, then executed on a device using a JS virtual machine and bridge. When developers think of the shortcomings of React Native, this setup usually comes to mind.
Without going into extraneous details, this choice in architecture means that in order to update the UI or change certain parts of the application, the written JS code needs to not only be interpreted but also added to the queue so it can cross the framework bridge to interact with native modules.
It’s not all bad for React Native; in fact, it’s the exact opposite. The team at Facebook has been working hard on a new architecture for React Native that addresses the main concerns outlined above. Notably, the deprecation of the bridge and introduction of JSI, a mechanism that allows direct communication between React Native's JS thread and native modules.
If you’re curious, read more about React Native’s “re-architecture”.
In the world of Flutter, things are very different. Unlike React Native, Flutter doesn’t rely on native components or views. Instead, Flutter paints every pixel on screen, giving developers full control of the painting and layout cycles. It might be easier to think of Flutter as a game engine but for building apps.
The framework is made up of five distinct layers:
- Material and Cupertino: The Material and Cupertino libraries offer comprehensive sets of controls that use the widget layer’s composition primitives to implement the Material or iOS design languages.
- Widgets: The widgets layer is a composition abstraction. Each render object in the rendering layer has a corresponding class in the widgets layer. In addition, the widgets layer allows you to define combinations of classes that you can reuse. This is the layer at which the reactive programming model is introduced.
- Rendering: The rendering layer provides an abstraction for dealing with the layout. With this layer, you can build a tree of renderable objects. You can manipulate these objects dynamically, with the tree automatically updating the layout to reflect your changes.
- Dart UI: Basic foundational classes, and building block services such as animation, painting, and gestures that offer commonly used abstractions over the underlying foundation.
Using aggressive composition, developers can use widgets from each of these layers to bring their applications to life.
The compiled result of this code is an application that’s fast and responsive. Because Flutter uses Dart as its language, applications are able to be compiled directly to native machine code, meaning there’s no need for a virtual machine or bridge in production. In addition, Flutter developers can also benefit from the Foreign Function Interface (FFI) for quickly and efficiently interacting with C (or any other compatible language) code in their applications.
Flutter doesn’t require a bridge in production to display UI, but it needs a bridge to interact with the underlying platform and plugins. The Google maps plugin is a good example of this.
Generally speaking, a compiled Flutter app is smaller and faster than a compiled React Native app; however, your mileage may vary based on your app’s complexity and your choice of dependencies.
The final and most important part of picking a framework is the community. After all, you’ll eventually encounter problems you can’t solve on your own, and having a community of developers who’ve solved some of the same issues is a major advantage.
Both platforms have come a long way since they were introduced and today, both can be considered mature ecosystems.
If you’re an avid Twitter user (or what I call the bird app 😉), then it won’t be difficult for you to find groups of developers passionate and eager to help newcomers to both frameworks. Flutter Community and React Native Community are both examples of excellent Twitter accounts sharing resources to help new developers.
Websites like Stack Overflow and Medium also have a healthy amount of Flutter and React Native content covering a wide variety of technical and non-technical aspects of each framework.
The number for React Native stories on Medium as of 6th Sept 2021.
The number for Flutter stories on Medium as of 6th Sept 2021.
The future of both platforms appears to be very bright. With React Native's change in architecture and roll out of JSI, the team at Facebook is breathing new life into a framework some thought to be in a slow decline.
Google Trend data showing the interest for Flutter and React Native over the last year.
A look at the Github star count for Flutter and React Native
Recently, the React Native team published a blog post signaling their intent to move beyond mobile and enter the desktop space. Personally, I’m very excited about this. It’s great to see more frameworks branch out and try new things.
For Flutter, the team at Google is doing a fantastic job listening to the community and introducing features that genuinely add to the Flutter development experience. The recent addition of null safety and the start of workaround data classes in Dart are both great examples of this.
Last week, Flutter released version 2.5, a major release that addresses some of the common bugs and recent performance concerns developers had with the platform. For more information on this release, please see What’s new in Flutter 2.5.
It's 2021, the world isn’t black and white. It’s very popular to choose sides in the mobile debate and argue that one framework is better than the other, but that's simply not the case. If you were hoping for this article to end with a clear "winner," I’m sorry to disappoint you but in real-world situations, it’s almost never that clear cut.
As a developer or company looking to build a mobile app, ask yourself the following questions:
- What is the use-case for my application?
- What are the strengths of your existing development team?
- Do you plan on sharing code between different projects?
- Are you building an app first or web first with a mobile client?
Ultimately, it depends on your project and the resources available to you. Not every developer or team is equal.