engineering

14min read

React Native vs. Flutter—What Is Better?

What is better?

This eons-old question in one of its myriad variations rears its head yet again, this time pertaining to the choice of (cross/multi)platform framework for mobile development, the one ring to rule them all.

Should I choose React Native or Flutter?

The conflict rages, both sides have their supporters and, as always on the Internet, there are hundreds of answers that contradict each other and offer you no help in making the decision.

Heck, why not throw in another?

XKCD comic about competing standards

  • Source: XKCD.com *

Should you believe me?

I mostly develop for Android, which I like even though it has its quirks, I like tooling dedicated to that, and I love Kotlin. That said, I’ve had my share of romancing with (cross/multi)platform frameworks. I’ve worked on React Native projects, both from an existing base and from scratch. I participated in an AR Unity project. I’ve experimented with creating Flutter UIs and have recently worked on a Flutter library, which had to use the system APIs for hardware handling.

I tend to be skeptical and try not to get hyped about things, though I sometimes fail in the latter.

While I can’t answer the question should you believe me, I hope that those few words of introduction will let you form an opinion on that.

Well, let’s get to it, I imagine you don’t have all day!

Similarities

The idea to write once and run everywhere isn’t even that fresh, and the two competitors from the title emerged long after their precursors, but because of that, their authors had time to learn from past mistakes and do things differently enough to gain significant traction in the mobile world.

Both solutions are based on similar concepts, both value composition over inheritance, and use declarative UIs. They are also both supported by tech giants and open-source, which gives hope for long term support and evolution.

Language

This point might seem weird since both frameworks use different languages, but there’s one important common element between them—they’re both not native to either of the platforms. This means that if you have an existing mobile team, its members will most probably have to learn a new language, and they might not be happy about it.

Both solutions are organized around components (or widgets—different name, same difference, at least at this level), which are composed of smaller components and yet smaller components until you get to the most basic ones, which are provided by the framework itself. These components are different from native views on a conceptual level, which might make the switch a little more painful, but the philosophy behind them just makes sense. It might be worth getting acquainted with it, especially with Swift UI and Jetpack Compose following suit.

text

Communication with native APIs

Since both frameworks run beside the native stack, they use mechanisms for asynchronous communication with the native stacks. In React Native, that would be methods annotated with @ReactMethod that accept Callback/Promise types or emitting events through RCTDeviceEventEmitter, while on Flutter those are MethodChannel with its MethodCallResult and EventChannel.

The advantage of Flutter here is that MethodChannel works both ways and lets you call Dart methods from native code as well as native methods from Dart, which makes the contract between the two a little cleaner.

Both depend on strings for passing method names.

Hot Reload

If you ever worked on a native mobile app, you must know the pain of waiting precious minutes for it to compile and checking how that one input box looks like after moving it a bit. And then making another small change again. And again. And again.

Well, hot reload takes care of that. It’s only seconds from making the change to seeing it on a screen, though it increases the start up time of the app. Benefits definitely outweigh the costs.

Differences

As always the devil’s in the details, and while both are similar in broad strokes, it’s the minutiae that matters most.

Language

But you have listed language in similarities!

I know, I know, but while Dart is interoperable with JavaScript and was created as its alternative, the differences here are gigantic.

  1. Static type system

    The most obvious is the support for language–level type system out of the box. While you can use TypeScript, ClojureScript, ReasonML, or at least Flow on top of JavaScript, they’re still transpiled into a JS bundle and must remain interoperable with JS. Their introduction into the project might prove non–trivial and, at least Flow, might introduce upkeep cost in the long run. Another issue with those would be that they’re less popular than JS, which might make recruitment or introduction of new project members into the project harder, and the same goes for looking for answers on Stack Overflow.

    Since at least some of the dependencies, you decide to use will use pure JS, you will either have to come to terms with the risks they introduce concerning type safety or create wrappers for them yourself.

  2. Execution

    React Native’s JavaScript bundle, which is the final artifact produced by any of the supported languages, needs to be executed by the JavaScript engine, which does just-in-time compilation, thus causing additional computational overhead.

    This summer, Facebook introduced Hermes, a new open-source engine optimized for React Native that uses ahead-of-time (AOT) compilation to bytecode that is then interpreted in runtime, increasing its performance.

    While Flutter’s Dart can be run in JIT (just-in-time) mode on its Virtual Machine (and actually is in debug mode, which allows for the hot reload), it also supports ahead-of-time (AOT) compilation to machine code, making it lightning fast on production.

  3. Quirks

    Dart doesn’t support overloading, for example. JS, on the other hand, has bizarre handling of this and closures and, in general, happily lets you type any rubbish and shoot yourself in the foot with a typo in a method name, which you’ll find only in the runtime. This, in turn, will nudge you into rethinking your entire career.

text

Flexibility in creating components

Flutter supports only one way of creating new widgets. From my perspective, that is a good thing.

React Native started in 2015 and was based on React, which was first released back in 2013. Since that time, there were new JavaScript standards, and the approach for creating new components has changed, but it cannot just drop support for the old ways. As a result, you can declare components in three ways: using a class, a function, or Hooks, the newest invention. If your team has previous experience with object-oriented language and is not aware of JavaScript pitfalls, it could be drawn to using classes_—that’s the easiest way to shoot yourself in the foot.

Hot Reload

While instantly loading code changes without waiting for the whole app to recompile is a great advantage over native in itself, it also had a pain point—the loss of state.

Until very recently, Flutter had a clear advantage here, since it was the only one to support stateful hot reload. So, you are not forced to retrace your steps to see that one little view ten screens into the flow of your application or tamper with screen order just to shorten the development cycle.

This has changed in September with the release of React Native 0.61 with a new feature called Fast Refresh. I did not have the chance to use it. It doesn’t support class components, which is a bad thing for legacy code, but probably a good idea to encourage moving to much clearer function components (with possible hooks).

Rendering

This is one of the most important differences, both on conceptual and execution levels. While React Native approaches the UI by wrapping the native views from both platforms into JS components, Flutter wants nothing to do with that. Instead, it bundles its own rendering engine. By doing so, Flutter makes sure it can optimize redrawing performances and that the UI looks the same on each supported device and even lets you switch “platform” on the fly, as I’m sure you’ve already seen in one presentation or another. If not, you might want to read this introduction to UI testing in Flutter.

While this makes Flutter faster and lets you worry less about the widget rebuilds, since they’re optimised under the hood, this might cause it to lag behind on platform novelties, or even skip them entirely.

React Native, on the other hand, is using those native views internally anyway, and it is fairly easy to wrap a native view in a React component on your own.

text

Speed

This has two aspects:

The execution speed

  1. As I’ve mentioned above, the Dart code runs faster, because it doesn’t introduce interpretation overhead.
  2. Since Flutter is using its own renderer, it avoids additional bridge–passthroughs.

The development speed

  1. Since views rendered by Flutter are platform agnostic, the developer knows that the layout will look the same on similar screens, no matter the platform.
  2. Type system lets you move faster with greater certainty, making less silly mistakes on the way.
  3. Type system and IDE support allow you to more easily get acquainted with widgets, without external help from docs site.
  4. First-class IDE support and generally better tooling lets you focus more on the problem at hand.

Dependencies

Both use a similar system with a file in a widely-used data-definition format defining dependencies. However, thanks to scripting magic, Flutter doesn’t require additional install steps when importing dependencies with native code. Nothing big, but a nice touch.

The last time I checked, an empty React Native application created through its generator installed over 800 modules to node_modules/. Those are JavaScript packages from www.npmjs.com, and while most of them will be development only, the rest is bundled alongside your code into the app. An App that displays a counter you can increment or just a hello world. Crazy.

JavaScript has a much bigger ecosystem, with tens of packages to solve almost every problem. That itself is a problem since there are a lot of dead packages, some possibly malicious, some probably duplicated.

Dart, on the other hand, is younger and less popular, but ever since Flutter’s official release, it is catching up. pub.dev, the Flutter’s dependency go-to place, has a built-in analysis tool that lets you see any issues it found in the package.

Tooling

I must admit I haven’t used many tools concerning React Native. I’ve used debugging and logging via Chrome a few times. However, it’s not perfectly dependable, since it switches the execution engine to that inside of the browser. This, in specific cases, might make tracking down bugs harder. I haven’t used a dedicated profiler, only the native ones. I’ve developed from Visual Studio Code, since it worked best for me, but each time I wanted to track where a function is used, I had to do a project–wide string search—far from ideal.

Flutter, on the other hand, has support for every Android developer’s favorite, the Android Studio, with its shortcuts, navigation, hints. It even lets you start iOS Simulator and run the app on it straight from IDE! There are context-aware actions, like wrap in or displaying a list of widgets you can use. Flutter plugin lets you track your widget hierarchy with all of its state. The debugging just works straight from IDE. The debug variant displays warnings right on the device’s screen when widgets overflow their bounds.

Not everything works as well as with native Android, but nevertheless, it is much better than my experience with React Native.

text

Security

JavaScript’s just-in-time compilation might make React Native applications more vulnerable to code injection attacks and the large number of dependencies for anything makes it harder to screen them all for vulnerabilities.

Stranger things

Since Dart is statically typed (with the addition of dynamic type, which can be anything and is useful in specific cases), its bridging supports more granular types, making your life easier. On the other hand, React Native provides more flexibility by supporting passing callbacks, which Flutter does not.

Because React Native does not guarantee that views look the same on both platforms, you actually need experts for all of the platforms, and now you have three (!) of them, though if you meet that condition, the development does seem to proceed at a faster pace.

Both solutions run, as a general principle, on one thread, and the bridging should/must happen on the main (UI) thread, which might cause throttling issues and impact performance if you must send over large quantities of data.

Flutter has isolates, which are physical threads to which you can move heavy processing to avoid hiccups on the user interface. Asynchronous operations on both can be done coroutine–style with async/yield/await trio or using Future/Promise.

React Native has a more standardized approach to state, with Redux and its store/action/reducer system holding the trophy. On Flutter, there are at least three ways of managing state competing, ie. Redux port, Business Logic Component (BLoC) concept using built-in or RxDart streams, and passing local state down through widgets provided by Flutter framework.

Redux makes tracking causality in code harder, BLoC is harder to read, and the passing of state might cause redundant rebuilds of the tree.

I’ve once integrated a Unity-based AR into a React Native application and its performance was… poor. Frames skipped, virtual objects jumped from time to time, animations were slow, though camera feed worked fine.

I’ve also once encountered an application where Redux state was treated like permanent storage (think: database). In the default implementation of the Redux store on Android, an attempt to write 6 or more megabytes of data cause the store to fail, dropping all of the stored state to disappear permanently. This turned out to be a serious problem that was hard to identify.

What to choose then?

The sun’s coming down, the show’s ending and I still haven’t answered the question. Even though the title suggests only two choices, remember that there are actually more. Without further ado—the summary.

When to choose React Native

I don’t like React Native, and I’d like that to make it crystal clear. That said, I do think it might be a good idea if certain conditions are met, and those are:

  1. You already have a strong web team

    Chances are your Front End and Full Stack developers already know React, and they’re pretty much the same with a bit changed names of the components on the far bottom. This means that you already got someone with the necessary knowledge that can teach the rest of the team how to avoid common pitfalls, knows how to organize architecture and how to use npm/yarn and manager dependencies.

    Be aware, you still need at least one Android and one iOS developer in the team for their knowledge of their platforms.

  2. Your app is porting a web app

    In this case, you might be better off with w Progressive Web App, but nevertheless, you could create a common module for components used on both web app and the React Native app.

  3. Your app is a content browser

    If your application is not really using sensor data or the native APIs from the platforms and the only permission you require is internet access—this might be the way to go. Especially if it’s light on animations and you don’t mind an occasional drop in the performance.

  4. Your app is not processing a lot of data

    Any I/O operation you want to do has to pass through the bridge. If you’re planning to make a lot of round trips, you should look elsewhere, but if that’s not the main feature of your application, you’re good to go!

When to choose Flutter

If I were to go cross/multi–platform, this would be my recommendation as from my perspective it wins with React Native in almost every department.

  1. You already have a mobile team

    Mobile developers, in general, come from statically typed languages, where all the little mistakes are caught compile-time or by IDE analysis before you even try to launch. They wouldn’t be happy about switching to JavaScript and losing good IDE support, but Dart is pretty similar to Kotlin/Swift that they’re used to.

  2. Your app doesn’t depend too heavily on the platform

    If you want to create AR/VR, then you’ll probably be better off with native. You only want Internet access, Bluetooth, or something like that? I’d say you’re set!

  3. You don’t care much about platform UI

    You’re making a beautiful custom UI with pretty and smooth animations? Go Flutter! Performance is great, and they’re going to look the same anywhere you launch. It is possible to maintain two UIs that feel the iOS or Android, but it’s not the point of this framework (of course I don’t mean things like the position of the navigation bar, since it’s super easy to write it once and have it change position depending on the platform).

  4. You want to save money, but performance matters

    Let’s face it: you’re thinking about crossplatform, because it saves money—shared logic, smaller team for the same scope. Flutter has a web spin-off called Hummingbird that lets you create web apps, but I have neither used it nor researched it—just letting you know it’s there.

The hidden options

Remember that you always have other options.

One would be going native with two teams. If your app does some heavy processing, you might consider going with native, accepting the increased cost and synchronization overhead, as it might give you the best performance.

Another such solution is to use Kotlin Multiplatform for shared logic between the two platforms. This still requires you to keep two separate native mobile teams, but since Kotlin is very similar to Swift, both teams should be able to easily write new logic and sharing it means lower cost of fixing bugs and evolving requirements.

I’d like to thank Piotr Dubiel for bringing me up to speed on the latest news about React Native

If you have a project, but you are not sure which way to go, don’t hesitate to contact us, and we will help you with the decision.

Mikołaj

Software Engineer

Did you enjoy the read?

If you have any questions, don’t hesitate to ask!