07/30/21

React Native VS Flutter

Intro

Having worked with both React Native and Flutter for roughly an equal amount of time, I’d like to share my personal experience (although I’m not claiming it will be true for everyone!)
This post is for:

  • People who are not familiar with these technologies but would like to understand the fundamental differences between them.
  • People who develop with React Native and want to know what they would gain by switching to Flutter.
  • People who develop with Flutter and want to know what they stand to gain by migrating to React Native.

General Information

Flutter

Developed by Google in 2017, Flutter is designed to develop applications for different platforms that can run natively on mobile or that can be embedded in existing web applications. Thanks in large part to Google's influence, Flutter is considered one of the leading open-source frameworks today.

Its advantages are numerous, like a complete development ecosystem, hot reloading functionality, and the fact that it's open source and free to use. However, it also has disadvantages, such as its large size and its dependence on native tools and technologies.

Flutter has been chosen by companies like Alibaba, BMW, eBay, etc. (https://flutter.dev/showcase). It is developing very quickly as a framework, and may soon become more popular than React Native.

Flutter doesn't use native components, so it has no layers to communicate with them. Instead, it draws the entire interface itself. Buttons, text, media elements, and backgrounds are all rendered inside the Skia graphics engine within Flutter. The Dart programming language is used as the development language. When building an app for deployment, Flutter translates Dart code into native app code using Dart AOT (compiling the app before running it), which can be run on many different platforms, including Android or iOS. However, when developing an app, Flutter uses JIT compilation (compiling the app while it is running) to speed it up.

Supported platforms:

Platform Version
Android API 19 and above
iOS iOS 9 and above
Linux Debian 10 and above
macOS El Capitan and above
Web Chrome 84 and above,
Firefox 72.0 and above,
Safari on El Capitan and above,
Edge 1.2.0 and above
Windows Windows 7 and above

Although Windows, macOS and Linux are in the stable channel, they are still in beta.

React Native

Developed by Facebook in 2015, the React Native framework is built for developing cross-platform applications, and is currently one of the most popular frameworks in the world. Why is it so popular? The answer lies mainly in its flexibility and the number of developers already familiar with ReactJS, which allows them to use React and JavaScript as well as native platform capabilities to create mobile apps.

This open-source framework has one of the world’s largest communities. It provides hot reloading, and also allows developers to use existing code written in other programming languages, such as Java or Kotlin for Android and Objective-C or Swift for iOS. Developers love the ease of use of plugins.

At the same time, React Native has disadvantages. For example, very complex interfaces built with React Native can sometimes be laggy or choppy. Nevertheless, it is a favorite of many important companies: Facebook, Microsoft, Artsy, Vogue, Bloomberg, Tesla, and others (https://reactnative.dev/showcase) have all built many of their mobile applications on React Native.

In the existing React Native architecture, there are three threads running in parallel:

  • JS thread. This is the thread where the JavaScript code is read and compiled, and is where most of the application logic runs. The Metro bundler combines all the JavaScript code into a single file and processes the JSX and TS constructs. This code is then sent to the JavaScriptCore engine which can run it.
  • Native thread. This is where native code is executed. This thread interacts with the JavaScript thread when you need to update the interface or refer to native functions. This thread can be divided into two parts. The first, Native UI, is responsible for using native UI building tools. The second, Native Modules, provides access to special features of the platform on which the application runs. It's ready to go as soon as the application is launched - for example, if a React Native app uses the device’s Bluetooth module, it will become active as soon as the app is opened - even if it's not actually in use on the current screen.
  • Shadow thread. This is where the layout of the application is recomputed. The Yoga engine, a proprietary Facebook development, is used here. The results of the layout calculations from this thread are sent to the Native thread, which is responsible for the interface output.

The Bridge module is used to organize communication between the JavaScript and Native threads. This module is written in C++ and is based on an asynchronous queue. When the bridge receives data from one of the parties, it serializes that data, converting it to a string, and passes it through the queue. When the data arrives at its destination, it is deserialized.

This means that all threads rely on asynchronous JSON messages sent across the bridge. Each party sends these messages with the expectation (but no guarantee) that the messages will be answered at some point in the future.

As the React Native architecture evolves, the bridge functions are being phased out and replaced by a new mechanism named JavaScript Interface (JSI). JSI allows full interaction between all threads. By using shared ownership, JavaScript code can run native methods directly from the JS thread - there is no need to use JSON to transfer data. This eliminates the problems inherent in using a bridge, such as possible queue overflow and asynchronous data transfer.

The migration to the new architecture was expected to be completed in the 4th quarter of 2020, but so far there is no information that this has happened.

Supported platforms:

Platform Version
Android API 21 and above
iOS iOS 11 and above

What do Flutter and React Native have in common?

Both Flutter and React Native are free and open source. Both allow you to develop cross-platform applications, and both boast:

  • Great support - Google and Facebook are continuously developing the platforms, fixing bugs quickly, and constantly updating documentation. As a result, both frameworks are reliable. On average, the Flutter repository gets about 100 commits per week, and the React Native repository gets about 40.
  • The ability to create user-friendly interfaces - although Flutter uses widgets, and React Native uses UI components, both platforms allow you to create user interfaces with excellent animation and smooth transitions between pages, and that are in no way inferior to native development.
  • Hot reload - developers can see changes in real time without having to manually update or restart the application.
    Example of a hot reload in Flutter
    Example of a hot reload in React Native
  • Faster development speed compared to developing natively. You don't have to develop an application for a specific platform.

Installation and settings

Flutter

To install Flutter requires a bit of manual work:

  1. You need to download Flutter from GIT.
  2. Add the full path to flutter\bin to the PATH environment variable

After that you need to configure Android Studio:

  1. Download and install Android Studio.
  2. Together with Android Studio you will install the Android SDK, Android SDK Command-line Tools and Android SDK Build-Tools, which are required for Android development.
  3. Install the Google USB Driver.
  4. Set up the Android emulator.

The official Flutter installation and setup instructions are 7 pages long.

React Native

React Native also needs to be installed manually:

  1. Install Node and the JDK. This can be done with the package manager chocolatey by running the command choco install -y nodejs.install openjdk8
  2. Install and setup Android Studio
  3. Add a new environment variable called ANDROID_HOME with the value of %LOCALAPPDATA%\Android\Sdk
  4. After that create an app using npm, e.g. something like npx react-native init AwesomeProject

React Native + Expo

If you use React Native + Expo, installation is very simple:

  1. Install Node
  2. Install Expo: npm install -g expo-cli
  3. Create a project: expo init AwesomeProject

The official instructions for installing and configuring React Native are about 7 pages, but if you choose React Native + Expo, the instructions are less than a page long.

As you can see, neither of these frameworks have a single-line installation or a single installation package. Installing Flutter and React Native requires about the same amount of work, but if you use React Native + Expo then it will only take about 5 minutes to get started.


Development

State management architecture

To manage the state of the application, we have several choices for Flutter:

  • Vanilla/Native state
  • Provider/Scoped Model
  • BLoC
  • Redux

Vanilla/Native state: we immediately rejected this for a big application with a complex UI, largely because there is no global state. The idea here is to write UI and business logic together. The state of the application is stored directly in widgets.

BloC is a rather complex architecture to use and it seemed redundant for our use case.

We have chosen a Provider/Scoped Model, since business logic is separate from UI, states are stored in classes responsible for business logic, and it’s easy to use.

We found React Native to be much more intuitive than Redux for our use case.

For most of our Flutter projects we have decided to use a classic MVVM architecture, and to use Provider as our state management system.

In React Native we used Redux, which has been perfect for each of our applications.

Redux is used by a lot of React developers, and because it's more popular than the alternatives, there are a lot of articles and tutorials on the internet for it. On the other hand, the approach in Provider is much simpler and more intuitive for any programmer new to it to pick up than Redux.

Language

Flutter applications are written in Dart. Dart is an object-oriented language, and its syntax resembles a mixture of C# and JS. Dart uses static typing, and has a very low entry threshold for programmers who have already worked with C-like languages. Flutter will also be easier to use for those who are used to developing native applications.

React Native applications are written in JS or TS, so for web developers, the entry threshold is minimal.

In my own personal experience, it's easier to write business logic using Dart. A typed language, in my opinion, is a big plus (in React Native we can use TypeScript).

User interfaces

Flutter applications use Dart to describe the interface (widgets).

React Native uses JSX, an extension to the JavaScript language.

Let's look at a simple application. The two screens in this app are called “Home” and “Profile”. The Home screen has a header, a button to go to the Profile Page, and a picture. The Profile screen has a header, a button to go to the Home page, an avatar, a name and a description.

Flutter:

React Native:

Let's look at the code:

Flutter

The application starts with the main method. In it we set the root widget. In the Social widget, we specify routing in routes:... and the start page with initialRoute:...

main.dart:

import 'package:flutter/material.dart';
import 'package:st/profile_screen.dart';

import 'home_screen.dart';

void main() {
 runApp(Social());
}

class Social extends StatelessWidget {
 @override
 Widget build(BuildContext context) {
   return MaterialApp(
     initialRoute: HomeScreen.id,
     routes: {
       HomeScreen.id: (context) => HomeScreen(),
       ProfileScreen.id: (context) => ProfileScreen(),
     },
   );
 }
}

In the HomeScreen widget we set the header in the appBar:.., and we define the body of the widget itself in the body:... field. The body field contains three containers: the container with the Profile button, the container with the HOME text, and the container with the picture. This is also where the component styles are described. For example - color: Color(0xff23397B), padding: EdgeInsets.all(20)...

home_screen.dart:

import 'package:flutter/material.dart';

import 'profile_screen.dart';

class HomeScreen extends StatelessWidget {
 static String id = 'home_screen';

 @override
 Widget build(BuildContext context) {
   return Scaffold(
     backgroundColor: Color(0xffb0c1957),
     appBar: AppBar(
       toolbarHeight: 40,
       title: Text(
         'Social',
         style: TextStyle(
             color: Colors.black, fontSize: 16, fontWeight: FontWeight.bold),
       ),
       backgroundColor: Colors.white,
     ),
     body: Column(
       mainAxisAlignment: MainAxisAlignment.spaceBetween,
       children: [
         Container(
           width: 400,
           margin: EdgeInsets.only(top: 20),
           child: RaisedButton(
             color: Color(0xff23397B),
             padding: EdgeInsets.all(20),
             onPressed: () {
               Navigator.push(context,
                   MaterialPageRoute(builder: (context) => ProfileScreen()));
             },
             child: Text(
               'PROFILE',
               style:
               TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
             ),
           ),
         ),
         SizedBox(
           height: 1,
         ),
         Container(
           child: Center(
             child: Text(
               'HOME',
               style: TextStyle(
                   fontSize: 40,
                   color: Colors.white,
                   fontWeight: FontWeight.bold),
             ),
           ),
         ),
         Container(
           child: Image.asset(
             'images/home-bg.png',
           )
         ),
       ],
     ),
   );
 }
}

The structure of ProfileScreen is similar to that of HomeScreen, with the header described in appBar:.. and the body of the widget in body:.... The body contains several containers underneath each other, which each contain buttons, image and text fields.

profile_screen.dart:

import 'package:flutter/material.dart';

import 'home_screen.dart';

class ProfileScreen extends StatelessWidget {
 static String id = 'profile_screen';

 @override
 Widget build(BuildContext context) {
   return Scaffold(
     backgroundColor: Color(0xffb0c1957),
     appBar: AppBar(
       toolbarHeight: 40,
       title: Text(
         'Social',
         style: TextStyle(
             color: Colors.black, fontSize: 16, fontWeight: FontWeight.bold),
       ),
       backgroundColor: Colors.white,
     ),
     body: Column(
       mainAxisAlignment: MainAxisAlignment.start,
       children: [
         Container(
           width: 400,
           margin: EdgeInsets.only(top: 20),
           child: RaisedButton(
             color: Color(0xff23397B),
             padding: EdgeInsets.all(20),
             onPressed: () {
               Navigator.push(context,
                   MaterialPageRoute(builder: (context) => HomeScreen()));
             },
             child: Text(
               'HOME',
               style:
               TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
             ),
           ),
         ),
         SizedBox(
           height: 50,
         ),
         Container(
           child: Center(
             child: Text(
               'PROFILE',
               style: TextStyle(
                   fontSize: 40,
                   color: Colors.white,
                   fontWeight: FontWeight.bold),
             ),
           ),
         ),
         SizedBox(
           height: 20,
         ),
         Container(
             child: CircleAvatar(
               radius: 130.0,
               backgroundImage: AssetImage('images/profile-image.png'),
             )),
         SizedBox(
           height: 20,
         ),
         Container(
           child: Text(
             'SARAH TAYLOR',
             style: TextStyle(
                 color: Colors.white,
                 fontSize: 20,
                 fontWeight: FontWeight.bold),
           ),
         ),
         SizedBox(
           height: 20,
         ),
         Container(
           color: Color(0xff000d4a),
           padding: EdgeInsets.all(18.0),
           child: Container(
             child: Text(
               "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla ut ultricies velit. Proin at nisi nisl. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Etiam eu tincidunt dui. Quisque non ornare ex, facilisis congue enim. In neque nulla, posuere at gravida id, dapibus et libero.",
               style: TextStyle(
                 color: Colors.white,
                 fontSize: 17,
               ),
             ),
           ),
         )
       ],
     ),
   );
 }
}

React Native

Like Flutter, React Native contains a description of the navigation and the initial screen in the initialRouteName field in the App.jsx file.

App.jsx:

import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
const Stack = createStackNavigator();
import HomeScreen from './Home';
import ProfileScreen from './ProfileScreen';

const App = () => {
 return (
     <NavigationContainer>
       <Stack.Navigator initialRouteName="HomeScreen" screenOptions={{ gestureEnabled: false }}>
         <Stack.Screen name="HomeScreen" component={HomeScreen} options={{ title: 'Social' }} />
         <Stack.Screen name="ProfileScreen" component={ProfileScreen} options={{ title: 'Social' }} />
       </Stack.Navigator>
     </NavigationContainer>
 );
};
export default App;

This screen describes the positions of the three main components - the Profile button (TouchableOpacity), the Home text (Text), and the image (Image).

Home.jsx:


	import React from 'react';
import { View, Text, StyleSheet, TouchableOpacity, Image } from 'react-native';
import HomeBG from './assets/home-bg.png';
import {default as styles} from './home-style.js';

const HomeScreen = ({ navigation }) => {
   return (
       <View style={styles.appStyle}>
           <TouchableOpacity
               style={styles.btnContainer}
               title="Profile"
               onPress={() => navigation.navigate('ProfileScreen')}
           >
               <Text style={styles.btnText}>Profile</Text>
           </TouchableOpacity>
           <View>
               <Text style={styles.heading}>Home</Text>
           </View>
           <View>
               <Image style={styles.homeBG} source={HomeBG} />
           </View>
       </View>
   );
};

export default HomeScreen;

This screen also has several consecutive components - the Home button, the Profile text, the picture, and a few more text boxes.

ProfileScreen.jsx:

import React from 'react';
import { View, Text, StyleSheet, TouchableOpacity, Image } from 'react-native';
import ProfileImage from './assets/profile-image.png';
import {default as styles} from './profile-style.js';

// The main component for the ProfileScreen
const ProfileScreen = ({ navigation }) => {
   return (
       <View style={styles.appStyle}>
           <TouchableOpacity style={styles.btnContainer} title="Profile" onPress={() => navigation.navigate('HomeScreen')}>
               <Text style={styles.btnText}>Home</Text>
           </TouchableOpacity>
           <Text style={styles.heading}>Profile</Text>
           <View style={styles.profileContainer}>
               <Image style={styles.profileImage} source={ProfileImage} />
           </View>
           <View>
               <Text style={styles.profileName}>Sarah Taylor</Text>
           </View>
           <View style={styles.profileBioContainer}>
               <Text style={styles.profileBio}>
                   Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla ut ultricies velit. Proin at nisi nisl. Class
                   aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Etiam eu tincidunt dui.
                   Quisque non ornare ex, facilisis congue enim. In neque nulla, posuere at gravida id, dapibus et libero.
               </Text>
           </View>
       </View>
   );
};

export default ProfileScreen;

I think JSX is less cumbersome and easier to read than Dart’s widget descriptions. It's not uncommon for a widget in Flutter to be several pages long, but in React Native this happens very rarely. Thanks to the HTML-like description of interface components in React Native, you can use style tags and CSS-like styling for elements with those tags, whereas in Flutter there is no such mechanism. The use of style tags means that JSX provides a very clean and readable interface description.

Native look and feel

Applications created with React Native take on a native look and feel. If the operating system is updated, elements of the app will be updated too, which will keep the app visually consistent with other native apps.

This also means that the app will look slightly different on Android and iOS devices, as well as on different versions of either OS. This can be seen as a plus or a minus, depending on whether you want the app to have a native OS look or if it needs to look 100% the same on all platforms. React Native does also allow you to create applications with styling that will look and feel the same across platforms, if desired - but this requires the creation of components on the native side of the app that are then connected to the React Native project. This takes extra effort and time.

Flutter has its own large set of widgets built in. This means that an app can be easily built without the need for third-party libraries and frameworks. Given that Flutter behaves the same on all OSs, it has an advantage when it comes to creating a user interface that needs to look the same on all platforms. Flutter also comes with two sets of widgets: Material Design, which emulates the native Android design, and Cupertino, which emulates iOS. In order to make your Flutter app look native on different platforms, however, you need to build two different versions of the UI, using different sets for each.

That being said, the decision to use either React Native or Flutter needs to be made based on the requirements of the app being built. If the application needs a fully custom interface, then Flutter would be the bestchoice. If the application needs to have a more "native" user interface, React Native is the best choice.

Debugging

There is a big difference between React Native and Flutter today. The Flutter team has done a very good job integrating their development tools with VS Code and Android Studio. Code debugging is seamless. Variable values are always visible, and the application doesn't have to be restarted if the code changes.

If you use React Native + Expo, you can run and debug both on iOS and Android using the same development environment and system (Windows, Linux, macOS). This makes the debugging process very convenient. You don't need macOS and Xcode to write code. The typical way of debugging in React Native is to run Google Chrome and use its console, which is very similar to debugging in web applications.

Platforms

Flutter supports platforms such as Android, iOS, web, Linux and Google Fuchsia. React Native supports Android, iOS, Web, macOS, Windows, and UWP.

It’s just as easy to generate a build of your app for Android as it is for iOS, and it’s a similar amount of effort to publish a new build on the Google Play or App stores.

Both Flutter and React Native have a similar problem when using them to build web apps: many third-party components for each framework won't work properly if used in web apps. This means that you can't just create a web build of your application straight away, in most cases - you'll have to either look for alternative third party components that will work on both mobile and web, or you'll have to write the components you need yourself.


Performance

The main performance metric for any graphics-heavy application is frames per second (FPS). The gold standard is 60 FPS, which will give the smoothest animations and screen transitions. When the FPS goes down to, for example, 30, it starts to become noticeable to the user.

The Flutter team states that they provide performance of 60 FPS on all devices and 120 FPS on devices capable of refreshing the image at 120 Hz. The React Native team claims 60 FPS.

The reason why Flutter supports greater FPS is the Dart language, and the fact that Flutter draws the entire interface itself instead of using native components. This avoids the layers required to interact with native components, giving better performance as a result.

React Native, on the other hand, uses a JavaScript bridge to exchange messages and events between the JS thread and the Native thread. Because of this, the performance is worse than Flutter.

From personal experience, I can say that very complex interfaces in React Native were a bit slow and I had to optimize or simplify them. In Flutter I've never noticed slowdowns, even on the most complex interfaces with many different elements shown simultaneously on a single screen.


Documentation

Although React Native has been around much longer than Flutter, the official Flutter documentation is more advanced, in-depth, and easier to navigate than the React Native documentation.

For example, it took me less than a minute to find the list of supported platforms in the Flutter documentation - the first link in Google leads to the official page. For React Native, I found information about the Android and iOS versions on GitHub, but I didn't find any information about macOS or Windows at all.

The Flutter documentation has "Getting Started" sections for developers with different technical backgrounds.

The React Native documentation does not have the same level of thematic organization and depth.

On the other hand, there is Stack Overflow and Reddit, where you can find answers to almost all questions.

I personally didn’t feel a huge difference in the quality of documentation, although Flutter’s documentation is more in-depth, which will be more useful for a junior developer.


IDE

Flutter allows developers to choose an IDE, such as Android Studio or Visual Studio. In React Native, you can use, for example, Visual Studio or WebStorm. Developers with experience developing native Android apps will find it easier to work in Flutter because they will be familiar with Android Studio, which is very user-friendly.

When working with React Native I used WebStorm and when working with Flutter I used Android Studio. I can say that these IDEs are very similar. Android Studio is based on IntelliJ IDEA.

Both products (WebStorm, IntelliJ IDEA) are developed by JetBrains. My feeling is that Android Studio is more oriented for mobile development. It has an emulator manager and it is easier to write native code in Java or Kotlin where necessary. There’s also one more undeniable advantage of Android Studio - it is free!


Community

Both Flutter and React Native have thriving communities, with many conferences, hackathons, and events held every year.

A statistic:

Flutter React Native
Stack Overflow questions 41,398 49,939
GitHub issues 9,144 open(47 396 closed) 1,492 open(19,994 closed)
GitHub stars 124,000 96,400
GitHub forks 17,800 21,000
Reddit members 74,100 78,347
GitHub public repositories 22,099 26,376

As far as we can see, although React Native is older, Flutter has almost caught up with it in many respects.

Switching to Flutter after React Native, I did not feel a qualitative difference in the community - there are a lot of articles and third party libraries for Flutter. I think the two communities are about equal now.


Development speed

The development environment, code writing, debugging - for me Flutter is the winner in all of these respects. For some things it’s only a slight advantage over React Native, for example in the development environment, and in other places it has a big advantage, like the debugging experience. On the other hand, React Native has a clear advantage in some areas, especially the clear and readable UI (jsx) descriptions and the ability to test iOS without macOS (using Expo).

As we see there is no clear winner in every aspect - it all depends on the application. It’s also worth keeping the language used by each framework in mind. For web developers, React Native will be much closer to what they’re used to, and development speed will be faster as a result. Programmers who are used to doing native development (Swift, Objective C, Java, Kotlin) or who have C# experience will find it easier to start with Flutter.


Summary

In my opinion, Flutter is a worthy competitor for React Native - it uses a typed language, has good documentation, and has a free IDE with constant updates and new features. It is developing more dynamically than React Native, and has already conquered the teething issues it was dealing with a couple of years ago. More and more big companies have started using Flutter as a framework for cross-platform development.

On the other hand, React Native is not far behind Flutter. React Native uses the most popular programming language - JavaScript. The code for the interfaces is in JSX, and (in my opinion) looks much simpler and cleaner than Dart. If you use Expo, development for iOS is possible without macOS, and the framework itself takes much less time to install and configure than Flutter. For myself, I drew these conclusions:

  • If you need to make an application with a really complex interface, you should think about using Flutter - it performs better under heavy loads.
  • If you want to get an MVP built quickly, I think React Native would suit you better. Interfaces in React Native are much faster and easier to make, and it's easier to find developers for a project written in React Native.
  • But if you know that your interfaces won't be too complex, and you don't need to build an MVP very quickly, then it doesn't matter which framework you choose.