Switching to React Native in Production on iOS and Android
Our team makes a fantasy football app called Sleeper. Last December, we made 2 big bets on our technology stack, both of which seem to be paying off. The first bet was on React Native, and the second one was on Elixir.
This post is about React Native. I’ll cover how we came to the decision to switch, the implementation phase, and the results in production.
If you just want to see how a full-blown React Native app feels in production on iOS & Android, feel free to grab our fantasy football app and play around.
The Decision
First off, it’s important to understand why we even made the decision to switch to React Native in the first place. We already had an iOS and Android app in production. We could have just kept working on those.
Plus, we also had some concerns about React Native, specifically around performance.
We were already aware that React Native had some shortcomings on Android. Our friends at Discord built a slick iOS app in React Native, but they chose to build their Android app natively due to performance issues.
Ultimately, we decided to build both our iOS and Android apps in React-Native for the following reasons:
- We are a startup with 2 engineers, so speed of iteration was critical to finding product market fit.
- We didn’t have the luxury to wait 2 weeks for iOS to approve an app just so we can fix a client-side bug. React Native would allow us update our app over the air and skip the approval process. (Note: iOS has since shortened their review times to ~1 to 2 days).
- We made a conscious decision that if we were going to succeed as a startup, we didn’t need to succeed on both platforms, just iOS, and we treated Android as a nice-to-have. Our Android app needed to be functional and useful for our users, but it did not need to have the same polish requirements as the iOS app. For some companies, this is a deal-breaker.
And so the decision was made. We scrapped our iOS & Android projects, went full react-native, and never looked back.
The Work
The biggest cost to switching to React Native was learning React Native. Client-side engineering is often difficult because languages change, and to get the desired result you want, especially in terms of user interaction / animation, you sometimes need to dive deep into the nuances of a framework.
Just a few years ago, it was basically 2 options for building iOS and Android apps— Objective C and Java. Now there is Swift, Kotlin, and React-Native, often times combined with Objective C or Java.
Our approach was simple. Glance over the documentation to see roughly what’s possible and what’s not, and then just try to build the thing and learn as we go, trusting in our ability to refactor if necessary.
We never tested on Android for the first 2 months of building Sleeper for iOS. Instead we got the iOS app to where we were happy with it, and then tried to run it on Android.
The first time you run your iOS React Native app on Android, you are probably shocked that it even builds. Then you will realize that it’s not nearly as performant as the iOS version (not even close), so you’ll need to do some tweaks, like removing certain interactions or animated transitions.
Overall, it took only one day for Ken, our founding engineer, to get it to a functional state that was at least usable. Let me repeat, we had a functional Android app within 1 day of extra work. I could hardly believe it, but yet it made perfect sense at the same time. I’ve read elsewhere that you should build Android concurrently with iOS, but I don’t buy it.
Focus on quickly getting a prototype of iOS working first, you can fix Android later, it’s not going to be that bad.
As we got more proficient with the framework, it became very easy to decide what was or wasn’t a risky interaction to build for Android.
My biggest learning from trying to do React Native on both Android and iOS is to target a lowest common denominator experience for Android (due to their plethora of device families), but build your iOS version exactly how you want it to be. Design and architect for a case where your app can degrade gracefully within the same code-base.
We definitely ran into hiccups with 3rd party libraries. A lot of them are just not very mature, as is the case with the ecosystem around most new frameworks. The React Native framework itself though has been solid and “as advertised.”
So after 3 months, we were finally ready to launch. But launching was something we did before, nothing special. What was special was seeing our first over-the-air update using Code-Push. We had finally experienced the holy grail of mobile app development — making live native changes to our app without App Store and Play Store approval.
One minor snag we did run into was error logging in production. We Googled for what other people have done and really didn’t like any of the tutorials written so far. We decided to go with Sentry as they have both source mapping and breadcrumb support for React Native, and have been able to identify issues in production within minutes.
The Results
Users love the app, and we’ve yet to hear any negative feedback on the app’s performance. Our users are quite vocal, so to not hear anything was surprising.
Last year our average feature cycle was 1–2 weeks. It’s now down to 1–5 days. We update our apps anywhere from 1–5 times a day now instead of once every 2 weeks. Iteration speed alone has helped us reach 5 stars in the App Store and improve in every single retention metric.
Our ability to respond to customers literally as they are chatting with us live is always surprising to them.
Code-Push also allows rollbacks so we’re never left hanging if there was a major problem with the release. Users who crash on installing an over-the-air update also get automatically rolled back.
We recommend putting the client version somewhere in the app (like settings) so you can help your users debug problems faster.
Sometimes, big technical changes are scary. You don’t know what to expect. But for us, the promise of React Native was simply too good to pass up if true.
Final Thoughts
I think if I had to name the single biggest benefit of React Native, is that it allows more room for error. Nobody is perfect, engineers make mistakes, and being able to quickly recover is critical.
If you are thinking about using React Native, I recommend you take Sleeper for a spin, as it’s one of the more complicated React Native apps out in the wild in terms of number of UI elements being rendered and also some of the user interactions. I hope this post has been helpful for you if you’ve been looking to make the switch.
We still have a lot of polish to do for the app, but it’s not a limitation of React Native, rather a limitation of our time, but we are quite happy with how the apps turned out, especially on iOS.
React Native is getting better with every version and I think the choice to use it is becoming easier.