« Simulated Annealing

Saving time with React Native

Actual developer data from launching a React Native mobile app

In July 2019, I launched the WorldAnimals app on iOS and Android. The app is a mobile game that teaches you different animal onomatopoeia from languages around the world.

The app idea was dead simple: three screens with easy to use interactions. First, a welcome title screen. Then, the main flashcard-style screen that would show the question (“What does a frog say in Japanese?”). When you tap on the screen, the answer is shown (“gero gero”), accompanied by a sound recording. And finally, a settings page.

app_design Three screens needed for the WorldAnimals app.

Before I even started coding, I knew the app concept was a great candidate for using React Native. Specifically, it was well-suited because the app needed only:

  • the same basic UI on Android and iOS
  • simple functionality, with no highly-optimized performance demands
  • efficient developer utilization, with only one developer available (me)

This post will dive into various stats and data gathered for this project, from ideation all the way to final launch of the product. I wanted hard numbers on how React Native helped streamline my development process. To that end, I made sure to time track the different tasks I worked on during the development of the app.

There isn’t much hard data on how much React Native actually reduces costs and improves efficiency. Some claim that the framework reduces development costs by 30-40% and requires only 20% of code to be changed. Others assert that React Native doesn’t really save you time and money in the long run and AirBnb famously sunsetted React Native because they did not find much improvement in the overall developer experience, among other problems.

So in the end, how much time did I actually save?

Background and inspiration

The original inspiration for the app came from one specific day in my Japanese language classes when I was living abroad in Tokyo. We started to learn the sounds animals made in Japanese–frogs say “gero gero” (ゲロゲロ ), dogs say “wan wan” (ワンワン), and cats say “nyan nyan” (にゃんにゃん).

nyan_cat That’s how I finally learned what the “nyan” meant in nyan cat.

To make sure the lesson was fun and engaging, my Japanese teacher went around the room asking all the students what sounds we used for animals in our native languages. The class was a diverse mix of people from around the world: Italy, China, the Philippines, France, Brazil, Russia, Norway and America. We spent the next thirty minutes sharing and laughing at how different the same animal could sound. It was one of my favorite memories while learning a new language.

I wanted other language lovers have the same experience, so turning it into a mobile app seemed like a great idea.

To make sure it was viable, I did some basic sanity checking by looking up the major world languages that also had easily accessible information on cross-linguistic animal onomatopoeia. I tried to pick the most common animals that still had distinct sounds in each language. I would be personally voicing the animal sounds used in the app in order to create a fun, consistent user experience and make it easy for young children who can’t read to still be able to play. Luckily my experience producing a podcast meant that I had plenty of recording equipment easily at hand.

I tried to loosely cross-reference the languages with the corresponding majority-speaking countries that had high mobile app usage, to make sure I had a good chance of high audience exposure and downloads once the app launched. Ultimately I chose the following 15 animals and 11 languages:

country_by_animals Which languages contain specific onomatopoeias for the given animal.

From the very beginning, using React Native made a lot of sense for this project. Platform popularity varies dramatically in the different target countries. While Android market share is less than 50% in North America, it has 70% market share in Europe and close to 75% in China, according to MacWorld. Focusing only on one platform would leave out a lot of users. On the other hand, building the same (admittedly, pretty simple) app twice was very unappealing as a solo developer. The promise of React Native is to be able implement an app on both Android and iOS, and implement it efficiently.

WorldAnimals was also an opportunity for me to try React Native in a new way. I have worked as an iOS developer for the last five years, and spent nearly a year researching and testing the React Native platform at Pinterest. But using the framework in a large existing production app is very different than building a mobile app using 100% React Native from the beginning. How much does the framework live up to its promise of cross-platform development in this context?

The Data

Overall it took 144 hours from start to final launch for the app or about 3.5 weeks developing full-time. In actuality, I worked on the app part-time for about 3-4 hours a day so it was finished in a little over two months.

If we break down the time by the different phases of production, coding took around 42 hours or 29% of total time:

total_time Distribution of total time taken to complete the app.

“Design” includes time spent prototyping the app UI in Sketch, designing the app icon and splash screens, and hand-drawing the animals and hats used in the app. The “Other” category consists of researching the animal sounds, as well as time spent exploring animation using Adobe After Effects and Lottie. Ultimately, I abandoned the effort for animated interactions because I am not a good designer and I also couldn’t get the Lottie-Sketch plugin to work.

As for “Marketing”, I started with a goal from the beginning to produce an ultra-high quality experience for users. Specifically this meant a really nice landing website and App Store/Play Store experience with nice promotional screenshots. In the past, I did not make any special effort to market previous apps so I wanted to make a good attempt this time around. I will dive into the learnings from the “Marketing” segment in a followup blog post.

Part 1: Building the iOS app

Now let’s zoom into the coding portion. I fully built the app using React Native on iOS first before porting it to Android. You can assume all non-Android-specific categories in the graph below indicates time spent building the app on iOS.

code_time Distribution of time taken to code the app.

Unsurprisingly, the largest chunk of time taken was getting the basic functionality working (12 hours). I used vanilla React Native rather than Expo since there was a lot of libraries included in Expo that I did not need, which would bloat the app size unnecessarily. But even with vanilla React Native, I had to manually comment out the geolocation API enabled by default in order to stop Xcode from complaining about location permissions.

It is difficult to estimate the exact difference in development time using React Native on iOS versus native Swift or Objective-C code. Because of my background as an iOS developer, I have stronger fluency with native libraries compared to the corresponding Javascript ones. On the other hand, React Native saves implementation and testing time with instant app reloading, fixing the problem of painfully slow Xcode compilation times.

One rarely-discussed downside of choosing React Native is losing out on tools in Xcode. Storyboards are a WYSIWYG tool that is useful for dynamic layouts and especially powerful for simple UI screens like the ones in WorldAnimals. Artsy’s explainer on React Native for iOS developers cites storyboards as one reason to keep some of their apps in native.

storyboards_resizing Adding resizing constraints is just a few mouse clicks using Storyboards.

I also couldn’t justify trying to use storyboards with React Native for this project. The minimal productivity gains of using storyboards come at the cost of huge overhead in terms of time and code complexity to bridge native files into Javascript, making it unappealing to have a hybrid app. Plus, these ported storyboards would be unusable in Android. And given that the entire purpose of using React Native is to be able to share code cross-platform, it is better off to build an 100% React Native app from the start.

Given these tradeoffs, I am assuming that it is essentially the same amount of time (as an iOS developer) to build an iOS app regardless of whether you use native or React Native. I was trading off proficiency with Swift/Objective-C and platform-specific tooling for faster builds and code iteration. Assuming a basic level of familiarity with Javascript, it is overall net neutral for a native developer to switch to using React Native on their home platform.

I do think there are significant positive benefits in using React Native for developers who have little to no familiarity with the native mobile platforms because it suddenly unlocks the ability to build apps without needing to learn an entirely new ecosystem. So web developers will find it much easier to get started on iOS by using React Native. Similarly, I saved so much time because I did not need to learn everything about the Android platform to build an Android version of WorldAnimals.

But before we get to Android, let’s talk about a few pain points when using React Native. The biggest surprise in post-analysis was the amount of time spent on accessibility (7.25 hours), localization (3.3 hours), and the loading animation on the splash screen (1.75 hours). I underestimated how unwieldy React Native was in these few cases due to subtle differences in the framework compared to the underlying mobile platforms.

cost_time Time spent in these categories were higher compared to native development.

For accessibility, it is not obvious how the accessibility properties provided by React Native will translate to native. Getting the proper sequence of accessibility labels read by VoiceOver is definitely more art than science even when using native APIs, and React Native just adds another layer of obfuscation. Plus there are many “iOS only” or “Android only” properties which demand a careful reading of the docs to get right.

Regarding localization, React Native does not provide a built-in library for localizing strings based on a user’s locale. Setting up react-native-localize and i18n-js is required if your app needs localization. This adds more setup and configuration friction that doesn’t exist when using NSLocalizedString on iOS or strings.xml on Android.

Finally, there is the problem of splash screens. There are still a few cases where you must leave the React Native development environment to dive into the native code and tooling. On app start, the underlying framework architecture first initializes the Javascript bridge and loads the JS bundle before any UI is shown. And because splash screens are shown immediately when the app is launching, developers need to have a native splash screen that is displayed while the JS is unavailable. While there are plenty of good tutorials that you can easily follow to set up a smooth splash screen experience, you may encounter some subtle bugs caused by rendering differences in native code and Javascript. I had issues with image positioning and ended up spending a lot of time making two splash screens look identical.

Transitioning from native to React Native splash screen may require layout adjustments.

Part 2: Building the Android app

In the previous section, I discussed why development time is largely equivalent whether or not you use React Native for single-platform development as an experienced mobile engineer. React Native becomes extremely compelling when you start building on the second mobile platform.

I recorded Android development time as time spent on android-specific UI and functionality (4 hours) and getting the android build to work (1.75 hours). Overall, only 13.7% of total coding time was spent on porting a fully functioning iOS app onto Android.

code_time Time spent building on Android as percentage of overall coding time.

Let’s ignore my inexperience on Android for a moment. Assuming that I could build a production-level native Android app in the same time as it took me on iOS, it would still have taken another 36 hours to rebuild WorldAnimals. This means that code reuse reduced the dual platform implementation time from an estimated 72 hours to 42 hours, saving me 41.6% development time.

android_time Visual representation of estimated time taken if I had developed both mobile apps using native.

Of course, 41.6% is a lower bound on time saved using React Native. I would have needed to ramp up on the Android platform before being able to build the app. A ballpark estimate on the time needed is another 40 hours to get up to speed, calculated by taking the average time needed for the top five Android development courses on Udemy.

I was able to reuse most of the Javascript code since the planned app design and functionality was pretty much identical between the two platforms. WorldAnimals has a total of 3028 lines of Javascript code (pulled with cloc):

~/code/world-animals/src (master) $ cloc ./

11 text files.
11 unique files.                              
0 files ignored.

github.com/AlDanial/cloc v 1.82  T=0.24 s (46.4 files/s, 13424.9 lines/s)
Language                     files          blank        comment           code
JavaScript                      11            102             53           3028
SUM:                            11            102             53           3028

There were about 165 lines of platform-specific code, or around 5.5% of the JS codebase. Most of the required changes were adapting the app UI for Android conventions, including:

  • Hardware back button handling
  • Using material design-compatible icons
  • Deep linking to Play Store instead of App Store
  • Minor margins and padding tweaks

For example, action icons are slightly different on iOS and Android. I updated the icons on the settings page to match commonly used design patterns on the respective platforms.

ios_android_ui Updating icons based on the mobile platform gives users a more native experience.

React Native provides an elegant way to check for the platform and use the correct icon and margins. The code snippet below renders the prompt for the users to share the app (“Share with friends and family”). Only lines 14-15 need to be updated to get the UI to look nice on Android:

return (
    <View style={styles.rowStyle}>
        <Text accessible={true} style={styles.settingsText}>Share with friends and family</Text>
                source={Platform.OS == 'ios' ? require('../images/share-ios.png') : require('../images/share-android.png')}
                style={[styles.settingsButtonStyle, { marginRight: Platform.OS == 'android' ? 4 : 0 }]} />

However, there were a few gaps in React Native that required additional work on Android. For instance, a poorly documented React Native bug caused a white flash when launching the app. The bug had a relatively easy solution on iOS while requiring a third-party library workaround for Android.

In addition, there was significantly more work needed to configure the Android build and publish to the Google Play Store. For the iOS-specific configuration, I needed about 35 lines of Objective-C code for the AppDelegate.m file and one WYSIWYG storyboard file to set up the native iOS splash screen. Android needed 223 lines of native code: 70 lines for android/app/build.gradle, 20 lines for MainActivity.java, and 133 lines of XML/Java to get the native Android splash screen up and running. That’s about 6 times as much native code on Android compared to iOS.

Furthermore, third party React Native libraries seem to have more problems on Android. I used react-native-sound to play the recorded animal sounds in the app. There are more than double the number of reported issues on Android (23 open issues) compared to iOS (10 open issues). Not only that, I also had to fix a bug where users stopped being able to hear sounds after 30 plays, which only happened on Android devices.

In general it does seem like Android is somewhat of a second-class citizen for React Native. The platform needs more complicated bug fixes and workarounds, requires significantly more time spent on build configuration and deployment, and opens up your project to more potential issues when adopting third-party libraries. And although I didn’t encounter this problem in this project, React Native reportedly has performance issues that affect Android much more than iOS. It is not clear to me if these difficulties arise from underlying complexities of Android itself or capabilities missing from React Native that might be addressed in the future.

The results

The actual recorded results actually showed even better gains than expected:

  Expected WorldAnimals
Time saved 30-40% >41.6%
Code shared 80% 86.9%

Be sure to take these numbers with a grain of salt. WorldAnimals is in some ways the ideal case for React Native, due to the simple and streamlined nature of the app. There are many situations where it may not be the best tool of choice. Even in my development journey for this project, I encountered plenty of stumbling points for the framework when compared to native.

Overall, React Native succeeded as a tool to improve developer productivity and efficiency. My one-woman team moved much faster with the ability to share code, easily test UI and make minor platform-specific tweaks, and skip learning all the nuts and bolts of a new mobile platform.

Stay tuned for the next post, where I’ll describe the mistakes and learnings in my attempt at amateur marketing the WorldAnimals app.

This article was last updated on 09/17/2019. v1 is 2,645 words and took 6.5 hours to write and 1 hour to edit. If you any feedback on this post or want to share your experience with React Native, you can send me a note here.