React Native at SoundCloud

About a year ago we faced an interesting question at SoundCloud: can we build SoundCloud Pulse — our app for creators — with React Native? Is a five-month-old technology mature enough to become part of SoundCloud’s tech stack?

A couple of months prior to this question, we started to design our second set of native apps for Android and iOS. We focused on iOS first because that is where the majority of the creators were. Finding iOS engineers proved harder than we thought, which is a situation likely familiar to you. For Android, we partnered with Novoda. We did not want to have many months between the iOS and Android releases. Meanwhile and independently, the design team was running user-testing sessions with React Native-based prototypes and we also had capacity from very eager web engineers.

Warming up

We had two main goals at the start. Including all teams whose input was valuable for this project and convincing ourselves of React Native. Input from our iOS team with many years of mobile app development experience was very valuable and reassuring. At the same time we were able to give them context as to why we were investigating React Native. Openly talking about the project in the company and including people in the decision making process helped us a lot in learning what React Native could and couldn’t do:

  • Which elements can and should be written natively?
  • Which elements can and should be written in JavaScript?
  • Can it compete with native apps in a way that users won’t notice it is “different”?
  • Does React Native support the native share dialogue?
  • Does it support deep linking?
  • Does it support securely storing access tokens?
  • Does it support Facebook & Google sign-in SDKs?

We also had questions about how well it would integrate with our existing native libraries:

  • Can we run acceptance tests with our existing iOS tooling?
  • How can we implement internationalization?
  • Can we integrate it into our continuous-integration pipeline?
  • What does the performance look like for long lists? In general?
  • Does it have a negative effect on battery life?

During a two-week spike, we built a prototype of the SoundCloud Pulse app to have a better understanding of React Native in practice. After the first week, we had already fleshed out most screens of the app and were surprised how quickly we were progressing. During the second week, we wanted to focus on the unanswered questions and write some native code. It was easy to bridge our existing iOS libraries with our React Native prototype. We completed the prototype with support from our iOS engineers and concluded that we had enough confidence in React Native to go ahead with the project full time.

The journey

We started working on the app with two JavaScript engineers and one iOS engineer. There were no designs for the iOS app yet but we knew that we needed to reach feature parity with the Android app and so we used the Android app as a blueprint for the iOS version.

It turned out to be a great idea to have an iOS developer working side by side with the JavaScript developers. There was a lot of knowledge sharing in the initial couple of weeks and we were able to keep up the fast pace from the spike sprint. We finished entire screens in single days and even built more features into the app than we had on Android in the beginning (e.g. 1Password integration).

In contrast to the spike sprint we focused more on achieving high test coverage. Coming from a mostly web background we were used to be able to deploy hot fixes within a couple of minutes in case of a runtime error. On mobile this is not possible and so we had to test more thoroughly before shipping a new version of our app. We thought about using services like CodePush that allow you to push an updated version of your app without needing to wait for the app store review process. However, all those services were not stable enough in the beginning and we decided not to use them to reduce the amount of sources of errors.

Development of the app started mid-October 2015 and we shipped the first version mid-February. It took us four months to write the app with three developers. Four months from a spike code base to a unit-tested, component-tested, and integration-tested application. We estimated that it would take us six months to finish the app but we underestimated how fast React Native allowed us to be.

Things we’ve learned along the way

Native libraries

For many popular native libraries / APIs / SDKs someone probably has written a bridge to React Native already. Of course the Facebook SDK is one of them, so is Google Signin but also things like Appboy, access to the iOS Keychain and many more.

If your biggest fear of jumping on the React Native bandwagon is that you have already amassed many great Objective-C libraries that you don’t want to lose, don’t worry, it is very easy to use them. React Native’s native modules are well equipped to bridge anything you have over to be accessed by JavaScript, and they are really quick to implement. It helps having someone with Objective-C knowledge in your team or someone who can help out, but the API is very understandable even for non iOS developers.

A perfect example of this was our event-logging library. It was battle tested in our listener app, it had support for batch sending events, so it would be lighter on the network traffic, and it could handle offline / bad connectivity. We were able to extract it from the listener application without too much effort. The rest was as simple as bringing a few methods like initialize, pageView and click to JavaScript. \o/

Native infrastructure

We didn’t need to reinvent the wheel when it came to building and testing our application either. Since React Native applications are built pretty much like any other iOS app (not counting the extra step of compiling JavaScript of course ;)) our iOS and testing teams were able to set up a CI environment very similar to the current structure within a couple of days. The acceptance tests were written in the same style as our iOS developers benefiting a lot from their experience and it making it easier to set up.

Another great help was knowledge around how to distribute an application to the app store and the process that revolves around that. Since we were mostly web developers and did not have any experience with that process, setting it up on the CI saved us a considerable amount of time running builds manually and uploading them to the app store by hand.

Web libraries

We could reuse a lot of our internal web modules with little to no modification. One good example is the way we handle internationalization. We are using the same library that we are using on all of our web-based applications, so we only needed to adapt our string parsing slightly.

Another example is a library for defining API endpoints in a centralized way. We were able to use it straight up, no changes necessary, by passing React Native’s fetch function as the transport layer. These were just two examples but there were many more small utilities / helpers that we were able to just drop in.

React Native Gotchas

Of course no technology is perfect and so React Native comes with its own set of small gotchas. None of them are deal breakers for us, but you should still know about them before starting a React Native project.

The biggest one by far is the fact that React Native is evolving very quickly, which of course is great, but it does incur a cost. There is a new version every two weeks (along with a release candidate of the next version) and the “Breaking Changes” section is often extensive. If you update on a regular basis you’ll be mostly fine; the bigger potential issues come from the third party libraries you use. As an example, now you import React from react instead of from react-native which is a problem if the third party libraries have not been updated to do the same. On top of that, Facebook definitely values pushing forward more than safely updating which was most apparent in an update to Babel 6 that broke many libraries and took weeks for everyone to catch up. All of this basically means you need to put a little more time aside for keeping up-to-date than with more mature frameworks.

The other little pain point is documentation. It is not as extensive as we wish and there are still some completely undocumented features / components, but this is getting better and you can see a big investment happening in this area.

Why React Native worked well for us

The main driver for us at SoundCloud to use React Native was the lack of enough mobile engineers to start development on a new mobile app. This is probably one of the main arguments for React Native if you are in a similar position of having more web engineers than mobile engineers. While this situation still holds true for us to some extent we have come to appreciate React Native for many more reasons than its JavaScript roots.

It is quite obvious that a better developer experience was a top priority for Facebook when creating React Native and it really shows. Automatically live reloading your app when your code changes massively shortens the feedback loop and using Chrome to debug your application feels very much at home to any web developer. If you also happen to use Redux and a logger middleware, knowing what is going on in your app at any point is very straightforward. It is a very comforting feeling to know the tools are there for you when you need them and its effect on developer happiness, development speed and confidence can not be understated. If you need one reason to give it a try, this is it. Happy developers and a faster turnaround are hard to beat. Product managers, decision makers & designers listen up, I’m talking to you here :)

But there’s more: the open source community provides many ready-to-use libraries and components and is very responsive to React Native’s changes, questions and improvement suggestions. It feels good to be part of this positive community.

Another huge benefit that we only recently started to explore is the cross platform nature of React Native. Facebook mentions about 85 percent code reuse between their iOS and Android implementation of Ads Manager, and with the right app structure that number is very realistic to expect. This means that once you have the structure there, building a new feature for your app on both platforms will only have an additional cost of 10 - 20 percent instead of 100 percent. That’s huge.

The ability to update your application on the fly, as mentioned earlier, also allows immediate bug fixes, a/b tests and the warm, cozy feeling that you can push an update whenever you want: a dream come true in the mobile development space (as long as Google and Apple allow it, which is always a little hard to predict).

What’s next

Based on the great experience we had with React Native, we plan on using it in more places at SoundCloud. Right now there is a team of designers working on a replacement for our prototyping / user-testing application written in React Native. Our designers found it much simpler to work on a React-based application than on a native application and are capable of building the application by themselves without frequent developer input. They are also working on bringing this application to Android to increase the pool of users that we can test with and to test Android-specific features.

Speaking of Android, we have spent some time to prepare the app for running on Android and got a stripped-down version of the app working in just a couple of hacker time sessions. We are currently investigating further steps in that direction, such as replacing the current Android Pulse app with the React Native version. Another area we are looking into is integrating React Native views inside our native applications. We’re looking forward to these experiments and hope they go as well as this one.

Get in touch with us

Jan Monschke
Peter Minarik @pietropizzi