BACKGROUND: Last week, Airbnb’s engineering team published a 5-part series on why they’re moving away from React Native. It’s well written, and worth reading if you’re looking for some context for this post’s discussion. All quotes you see in this article are from the Airbnb writeup.
As you might imagine, Airbnb’s recent announcement piqued our interest on the NativeScript team. Airbnb’s feedback on React Native was very technical, and interestingly delved into areas where React Native and NativeScript take different approaches to app development. Therefore, as we read through the series we couldn’t help but wonder—would Airbnb have fared better with NativeScript?
In this article we’ll walk through Airbnb’s complaints in detail, and talk about how some of those same problems could’ve been handled in NativeScript. We’ll start with things that NativeScript does well (this is the NativeScript blog after all), and then move on to things NativeScript does, well, less well.
Let’s start with what is perhaps the single biggest difference between NativeScript and React Native—how each framework accesses native APIs.
“React Native has a bridge API to communicate between native and React Native. While it works as expected, it is extremely cumbersome to write.”
Animated for animations, and in NativeScript you use an API named
Animation. But despite slightly different names, the APIs provide very similar functionality (animating user interface components). In most cases these frameworks’ cross-platform APIs are similar, and in some cases they’re even identical—for example, both frameworks provide support for the web’s
In React Native, to access native iOS and Android APIs you need to use a series of APIs commonly referred to as the bridge APIs. (Here are React Native’s bridging docs for iOS; and here are the same docs for Android).
“These bridges were some of the more complex pieces because we wanted to wrap the existing Android and iOS APIs into something that was consistent and canonical for React. While keeping these bridges up to date with the rapid iteration and development of new infrastructure was a constant game of catch up, the investment by the infrastructure team made product work much easier.”
Another consequence of this architecture is organizational, as not only is your code in three different places, but that code might be written by three different teams that specialize in each platform.
“In React Native, we started with a blank slate and had to write or create bridges of all existing infrastructure. This meant that there were times in which a product engineer needed some functionality that didn’t yet exist. At that point, they either had to work in a platform they were unfamiliar with and outside the scope of their project to build it or be blocked until it could be created.”
“In addition, large amounts of bridging infrastructure were required to enable product engineers to work effectively.”
3 on Android.
If you’ve developed iOS or Android app using any cross-platform framework, you know just how often you need to access native APIs to tweak the behavior of your app. You probably don’t need
java.lang.Math.min, but you might need to tweak your app’s status bar, configure how your app uses the native keyboard, or build an entire plugin for your app that abstracts these native APIs for others.
Writing your code in one language has some auxiliary benefits as well, such as less context shifting during development, better tool reuse (prettier, linting, etc), and the ability to stay in one IDE or editor. The advantages are appealing enough that Airbnb was trying to recreate a NativeScript-like approach in late 2017.
“We began to investigate automatically generating bridge code from TypeScript definitions towards the end of 2017 but it was too little too late.”
And speaking of TypeScript, let’s move on to discuss another common pain point when working with these frameworks—type safety.
Type conversion between multiple languages is not fun regardless of what platform you use, but we think we have a bit of an advantage in how we handle things in NativeScript—and that advantage is TypeScript.
I have mixed feelings about using TypeScript in web apps, and I’ve even given a talk on the subject, but for mobile apps I now consider TypeScript an absolute necessity because of how well the language helps you use unfamiliar APIs (which are hard to avoid in mobile apps).
In NativeScript we built our cross-platform modules with TypeScript since day one, meaning, you can easily explore the APIs we offer right in your editor.
If there’s any downside to TypeScript it’s that it’s yet another tool for you to maintain, update, and integrate. I personally haven’t run into these issues in my own NativeScript apps, but Airbnb mentioned problems when they attempted to evaluate TypeScript.
“We also explored TypeScript but integrating it into our existing infrastructure such as babel and metro bundler proved to be problematic. However, we are continuing to actively investigate TypeScript on web.”
Let’s shift our discussion from working with native APIs over to a user interface component that’s extremely common in mobile apps—lists.
A whole lot of mobile apps are nothing more than glorified lists—think Facebook, Twitter, etc. The list is a cornerstone of a mobile app development framework, and it’s something that has caused problems for React Native from the beginning.
“[Long Lists] React Native has made some progress in this area with libraries like FlatList. However, they are nowhere near the maturity and flexibility of RecyclerView on Android or UICollectionView on iOS. Many of the limitations are difficult to overcome because of the threading. Adapter data can’t be accessed synchronously so it is possible to see views flash in as they get asynchronously rendered while scrolling quickly. Text also can’t be measured synchronously so iOS can’t make certain optimizations with pre-computed cell heights.”
UICollectionView APIs that Airbnb mentions. These are extremely powerful built-in native APIs for building memory-efficient lists that asynchronously render rows as the user scrolls. However, React Native cannot use these APIs because the native components (
FlatList are quite good, but it’s hard to build a custom solution that can compare with the built-in native controls.
In NativeScript we use a single threaded model, as it’s what allows us to provide fast access to the native APIs that we discussed earlier. This means that we’re not subject to the same restrictions as React Native, and that we can absolutely leverage native APIs like
UICollectionView. To give you a sense of what that looks like, the image below shows what happens if I just casually toss 50,000 items into a NativeScript list. (It’s worth noting that what I’m showing here is not the power of NativeScript; it’s the power of the built-in native controls.)
NOTE: You can check out the above app’s source and run it for yourself in NativeScript Playground.
Two things though. First, NativeScript does offer APIs for offloading these CPU-heavy tasks when you do hit them. And second, NativeScript’s architecture is consistent with “raw” native apps you write with Swift or Objective-C on iOS, or Java on Android. By default, native code runs on the same thread as native UI, so CPU-intensive operations in native code can also degrade app performance and cause dropped frames.
And, as it turns out, which version of these VMs you use can have a huge impact on the performance of your applications.
At NativeScript we’ve been guilty of letting our VM version languish for this reason, BUT, we very recently updated to a modern version of V8 and got a huge (~50%) boost in startup performance on Android. For example, the image below shows the difference in startup time between a NativeScript app before the V8 update (left), and after the V8 update (right).
But, to try to be fair here, I think it’s worth mentioning a few things that I think Airbnb would’ve not liked about NativeScript before we draw any conclusions.
“Our website is built primarily with React.”
Airbnb talks extensively about how their use of React on the web contributed to them using them React for their native apps. They also talk about how, towards the end of their React Native usage, they started to investigate code sharing between their web and native apps.
“Late in the React Native exploration, we began building for web, iOS, and Android at once. Given that web also uses Redux, we found large swaths of code that could be shared across web and native platforms with no alterations.”
Over at NativeScript we’ve seen how important framework consolidation can be for a company. The initial version of NativeScript offered no framework support, and although we certainly had usage, when we unveiled integrated Angular support back in April of 2016 we saw an enormous jump in NativeScript usage.
We’ve seem a similar jump in usage when the NativeScript community released Vue.js support for NativeScript. It’s amazing how much more productive developers can be when you already know how concepts like navigation, data binding, and such work out of the box—especially when you have the ability to share code directly between the web and native apps.
That being said, NativeScript does not offer any support for React, which is not ideal for companies heavily invested in React like Airbnb. Supporting frameworks is a lot of work, especially when you think about all the tooling, templates, documentation and such that framework support entails. At NativeScript we’re very happy with our current Angular integration, and we’re working with our community to support Vue.js. Plus, we don’t think we can do React better than Facebook 🙂
“One thing that was immediately obvious when switching from React Native back to native was the iteration speed. Going from a world where you can reliably test your changes in a second or two to one where may have to wait up to 15 minutes was unacceptable.”
That being said, NativeScript’s LiveSync (which you see in the gif above) is super fast and supports developing on as many devices as you can connect. With NativeScript you see most changes within a second, and not the 15–20 seconds common in native apps.
NativeScript does not offer hot reloading yet, but it’s something we’re actively working on, and we’ll soon be able to measure our update cycles in milliseconds. Stay tuned to the NativeScript blog for details on future plans and releases.
If you’re a company with large existing native iOS and Android apps, the idea of rewriting that entire app to a new technology stack is oftentimes a non-starter.
“We integrated React Native into large existing apps that continued to move at a very fast pace.”
Although we at NativeScript do offer the ability to run within existing apps (here are our iOS docs; here are our Android docs), our support is currently experimental in nature, and not an officially supported usage scenario.
Meanwhile, the React Native team has heavily invested in this space and has a lot of battle tested apps using this approach in production scenarios (including Airbnb). Furthermore, Facebook has stated better integration with native apps will be a focus of their upcoming rearchitecture.
Once again though, React Native’s architecture is not without its problems. Integrating a framework like React Native or NativeScript into an existing app can be complex (just skim the React Native docs to get an idea), and the inherent complexity can cause problems, especially as iOS, Android, React Native, NativeScript, etc continue to evolve. We believe some of Airbnb’s problems came from trying to integrate React Native into a complex existing app, rather than starting a new React Native or NativeScript app from the ground up.
At NativeScript, we listen to our users to help prioritize features like this. If you’re interested in seeing us invest more time and resources into this workflow, please let us know on this GitHub issue.
So given all of this, would Airbnb have been more successful with NativeScript?
Obviously we’ll never know, but we do believe NativeScript would’ve helped with several of the issues that Airbnb encountered, such as working with native APIs, dealing with types, and building fast lists. That being said, NativeScript doesn’t support React, and doesn’t offer the same level of integration into large existing mobile apps.
Like any big technology decision, it’s usually a good idea to build proof-of-concept apps with all of your options before embarking on a full-blown development project. With that in mind, if you’d like to give NativeScript a shot, start with the tutorials in NativeScript Playground, our browser-based experience that’ll help you get up and running quickly. And if you have any questions about NativeScript from this article, feel free to reach out in the comments.