A Developer's Dilemma: Flutter vs. React Native
A few years ago, if you or your team decided to develop a mobile app for both iOS and Android, you would be required to maintain two codebases, one for each platform. This meant either learning or hiring developers well-versed in Swift and/or Objective-C(for iOS) and Kotlin and/or Java(for Android), the primary programming languages used to build native apps for these platforms. As you can imagine, this not only puts a strain on your resources and budget allocation but also involves longer development cycles and painstaking efforts to ensure consistency between both apps.
With the advent of cross-platform app development frameworks in the last few years, mobile app developers and companies are increasingly moving or at least considering using these technologies to build their mobile apps. The primary reason is that these frameworks enable developers to write code only once and build for both Android and iOS. Maintaining a single codebase significantly reduces your time-to-launch and ensures a high level of consistency between both apps. We will discuss in detail in a bit how this is made possible by React Native and Flutter, which are the two most popular cross-platform mobile app development frameworks at the moment. React Native is a Facebook(Now Meta) project and was released publicly about two years before Google released the initial version of Flutter in 2017. Before the emergence of these two frameworks, there were a few other options for developers, like Xamarin, Cordova, and Ionic, but they have since fallen out of favor because of many complexity and performance issues. There is a relatively recent effort by the JetBrains team(the company that built some of the popular IDEs) called Kotlin Multiplatform mobile. However, it is still in Beta and not as popular as React Native or Flutter.
If you are new to mobile app development or have yet to work with any of these cross-platform technologies, naturally, the question arises: Which one should I use or invest my time learning? My team at BrewApps has been developing mobile apps using both Flutter and React Native(and Native apps as well) for many years now, and we have often pondered on this question. In this article, I will attempt to highlight some key differences between React Native and Flutter's architectures, internal working, strengths, and weaknesses. I hope it will help you or your development team decide which platform to opt for in your next mobile app development project. Even though Flutter and React Native are designed to work on platforms other than iOS and Android, e.g., Windows, Linux, macOS, and Web, etc., for this article, I will limit the discussion to only iOS and Android.
What Programming Language(s) Do I Need to Learn?
React Native uses JavaScript, which is a widely used programming language, esp. in the web development world, and therefore if you are familiar with JavaScript or React JS, you will find it a lot easier to grasp React Native concepts. On the other hand, Flutter uses Dart, an object-oriented, type-safe, garbage-collected programming language developed by folks at Google. Dart's syntax may seem familiar due to its similarity with other programming languages, but the learning curve might be a bit steeper. If you come from the C/C++ world, you may feel more at home with Dart's type-safety requirement.
Because JavaScript has been around for quite some time, it boasts a much larger developer community and a mature ecosystem of libraries, frameworks, and tooling. On the other hand, Dart is a modern and relatively new programming language optimized for UI. Dart was chosen for Flutter because Dart's runtime and compilers support a JIT-based compilation that allows for features like hot reload and also Ahead-of-Time(AOT) compilation that generates efficient machine code suitable for production deployments.
Both languages support asynchronous programming, which is a key requirement in building reactive UI in mobile apps. In JavaScript, we have async, await, and Promises to allow asynchronous execution of code, and similarly, in Dart, there are async, await, and futures keywords. Dart has support for concurrency and parallelism through what is called an isolate(essentially an abstraction over threads and processes). In JavaScript, even though concurrency is achieved using Web Workers for web development, for React Native, you will have to use third-party libraries.
Now you might wonder, we are using JavaScript or Dart here, not Swift and/or Objective-C for iOS and Kotlin and/or Java for Android(used for native app development); what happens when you hit the Build or Run button? To understand that, you may want to learn a bit about React Native and Flutter's architecture and internals. Knowing this is not mandatory for developing a React Native or Flutter app in most cases. However, a deeper understanding of how things work may help you make the right design and architectural decisions in a large and complex app development project. Feel free to skip the next section if this doesn't interest you at this time.
What's Happening Under the Hood?
React Native
React Native is undergoing major upgrades to its architecture, and changes have started rolling out in the last few months. We will talk about what necessitated the changes in a bit. There are plenty of apps out there still using the old architecture and potentially in the process of migrating to the new architecture. So I think it makes sense to talk about both here.
In the old architecture, the best way to think about how this works is to imagine two worlds, JavaScript and Native, and a bridge component connecting these worlds.
The bridge enables asynchronous bidirectional communication using JSON messages.
You, as a React Native developer, will write all the business logic, callback handlers, etc., in JavaScript. JavaScript sends all the information(usually as a batch of messages) about the updates that it expects to be rendered to the Native side via the bridge. The Native platform APIs take care of rendering the Views. Whenever a UI interaction occurs, that event is sent to the JavaScript side, and the corresponding handler is executed. Using this bridge mechanism, React Native essentially has access to all the native APIs and thus controls the views of the underlying platform(iOS or Android). And more importantly, this is achieved without having to write any native code, i.e., Objective-C/Swift for iOS and Java/Kotlin for Android.
React Native comes bundled with an open-source optimized-for-React Native JavaScript engine called Hermes. The JavaScript VM runs the JavaScript code in your React Native app.
Even though the bridge in the old architecture is a clever way of facilitating communication between the JavaScript and Native worlds, it inadvertently becomes a bottleneck due to practical bandwidth limitations of the bridge and impacts overall performance in certain use cases. Furthermore, message passing involves serializing and deserializing operations that introduce processing delays, thereby affecting the user experience. If your app has complex high FPS animations or requires interactions like fast scrolling, it may not work very smoothly in React Native.
The new architecture attempts to address these issues by getting rid of 'the bridge' altogether and introducing a new mechanism called the JavaScript Interface(JSI). JSI allows JavaScript to hold a reference to host objects and invoke methods on them and vice versa. This eliminates communication overheads(message passing between the two worlds) and allows for synchronous execution and concurrency.
The new architecture, available since version 0.68, is built upon what the React Native team calls 'two pillars of the new architecture.'
1. The New Native Module System: Turbo Modules
Native modules allow you to access a native platform API that's unavailable in JavaScript. This happened in the old architecture through the bridge mechanism as described above. Turbo Native Modules are an improvement over the legacy Native Modules by providing strongly typed interfaces, the ability to write code in C++, lazy loading to facilitate faster app startup, and the use of JSI, as explained above.
2. The New Renderer: Fabric
The fabric rendering system written in C++ is designed to be cross-platform and more interoperable with iOS and Android, with the goal of improving user experience. The fabric takes advantage of the lazy loading, JSI, and other benefits the new architecture offers.
What Happens When You Launch Your React Native App?
When you launch your React Native app, the underlying operating system creates and assigns your app a Native thread, also known as the Main thread or UI Thread. The Native thread then sets up native infrastructure and spawns the JavaScript VM, which runs the JavaScript(your app and the framework) code. The Native thread then asks the JavaScript thread to process the main component that's registered as an entry point in AppRegistry, and communication between the JavaScript and Native world then commences.
Flutter
Flutter's architecture is very different in comparison to that of React Native. Here's a graphical representation of Flutter's layered architecture.
As a Flutter app developer, you will spend most of your time on the first two layers writing your application's business logic in Dart and using the libraries and services available in the framework layer.
In Flutter, it's said that "Everything is a Widget," which essentially means that you build your UI screen out of widgets. Flutter provides an extensive suite of basic widgets. Every UI aspect is described as a widget, and widgets are nested inside each other to build a widget tree. The layer below, the Flutter engine(written in C++), is mainly responsible for low-level input/output primitives and translating all the UI descriptions to actual pixels(aka rasterization). It does it through the use of the Skia graphics engine. So instead of relying on the platform-provided widgets, Flutter uses its own high-performance rendering engine to draw the 'Flutter widgets.' Finally, the lowest layer in the stack is a platform-specific embedder which acts as the glue between the host operating system and Flutter. The embedder coordinates with the underlying operating system for access to services like rendering surfaces, input, etc. There will be a separate embedder for each target platform, e.g., one each for iOS and Android.
What Happens When You Launch Your Flutter App?
When you launch your Flutter app, the embedder component we discussed earlier provides the entry point and initializes the Flutter engine. The Flutter engine is responsible for managing the Dart VM and Flutter runtime. Rendering, input, event handling, etc., is then delegated to the compiled Flutter and app code.
In addition to these main differences in the architecture between Flutter and React Native, the important thing to note here is the Dart code is ahead-of-time(AOT) and compiled into native, ARM, or x86 libraries when deployed to production. The library then gets packaged as a "runner" project, and the whole thing is built into a .apk or .ipa. Also, unlike React Native, Flutter doesn't use built-in platform widgets but widgets from its own library(Material Design or Cupertino widgets) that are managed and rendered by Flutter's framework and engine.
What If I Need Something the Platforms(iOS or Android) Offer But Is Not Exposed in Flutter or React Native?
In React Native, with the legacy architecture, you can write Native modules to access platform APIs that are unavailable in JavaScript. The Native modules can either be built inside your React Native project or separately as an NPM package. With the new architecture, the legacy Native module is being replaced with Turbo Native Module and Fabric Native Components.
With Flutter, you can write plugin packages for Android (in Kotlin or Java) and iOS (in Swift or Objective-C). In addition to this, you can also write platform-specific code in your app and have the Dart portion of your app communicate with the non-Dart part using what is called a platform channel. This message passing between Dart and the non-Dart portion is similar to the message passing across the bridge in React Native.
Debugging and Dev Tools
React Native and Flutter have many features and developer tools for debugging and performance monitoring.
Both React Native and Flutter support "Hot reload," which essentially means when you run the app in debug mode on the simulator, any change you make in the code is reflected near instantly in your simulator window. This is very convenient as you don't have to recompile the entire app every time(and wait for it to finish) you need to inspect how minor changes in your code would affect the UI or functionality. In React Native, the Hot reload feature is called 'Fast Refresh.' Hot reload in Flutter is possible because of Dart's ability to use its VM to JIT compile the code.
In React Native, you can use the JavaScript debugger in the Chrome browser's developer tools to debug JavaScript code by setting your project to be debugged remotely. Debugging through the native code can be done by launching your app in Android Studio or Xcode and using the available native debugging features in the IDE. Some developers find this setup a bit clunky.
Flutter comes with an extensive suite of performance and debugging tools. These packages and tools can be directly installed in popular IDEs like IntelliJ, VS code, etc.
What Does the Developer Community Say About Flutter and React Native?
Let's look at some of the Stack Overflow developer surveys over the past two years.
When asked about which frameworks and libraries have you done extensive development work in over the past year and which do you want to work in over the next year?
2021
2022
When asked about which technology they were learning in 2022?
These surveys show that developer interest in both React Native and Flutter remains high, and a slight increase in Flutter recently. Google trends over the past year confirm this.
Which Companies Are Using React Native or Flutter for Their Mobile App Development?
The list of companies using these cross-platform technologies is ever-growing. Here are a few notable mentions:
React Native
- Shopify
- Wix
- Tesla
- Flipkart
Flutter
- BMW
- Nubank
- Google Pay
- Dream11
- Toyota
I Am Convinced React Native and Flutter Are the Future of Mobile App Development. How Do I Get Started?
The official documentation of React Native and Flutter is a great resource for learning and setting things up to develop your first cross-platform mobile app.
Following are a few other resources, amongst many others, that have good content for learning and getting started.
- Popular Udemy course for React Native by Maximilian
- Another Udemy course by Stephen Grider
- Flutter tutorials at Codewithandrea
- Flutter fundamentals Tutorials and Videos by Ray Wenderlich
- YouTube videos for Flutter development by Marcus Ng
Final Verdict
Both frameworks have their own set of strengths and weaknesses, are backed by Big companies like Google and Facebook, have a thriving developer community, and are here to stay for a long time. Looking at the developer surveys and based on our own experience building apps using both of these technologies, it seems like Flutter has a slight edge over React Native at the moment because of its rich library of widgets, developer-friendly tooling, and overall performance. It, however, comes down to your specific requirements for the app, resource availability, as well as the expertise and preferences of the development team. This article will hopefully help you assess that and make the right decision.