Our React-native experience at Drops (so far)

It was interesting to see the negative sentiment that the Airbnb article has created around Facebook’s mobile framework — it hit me completely unexpected.

I started questioning how we ended up having a completely different experience than them…

To give a bit of background, we’ve spent the last 12 months building probably the most polished React-native app, and it was painful. We’ve been sweating on performance issues, writing our own libraries, patching others’, dealing with edge cases only reproducible on a specific device, wasting time on upgrading, etc…

But… was it less painful than writing native code?

We’re a small, senior development team of 4— Anton, Daniel, Lucas and me. We work at Drops, and serving more than 800,000 monthly active users, on iOS & Android. If you fancy learning (human, haha) language, check it out!

The most important factor for us is that we need to work at the right abstraction level. But hey, let’s not jump ahead. I want to share the struggles first, because they were not the ones we were expecting.

(Actual) problem #1: Navigation

Navigation is an essential part of all mobile apps. It was exciting to try out all the different libraries, but we haven’t found anything that would satisfy our main criteria: only render the currently active screen. Having multiple, heavy screens rendered on top of each other made performance an issue, especially on lower end Android devices — so we’ve written our simple, 30 line routing mechanic, that renders only one screen (that the user can see right now) & has special handling for modals. This resolved our major headache, and also made routing entirely declarative (but that’s a story for another post).

The funny thing is that we had an unconventional navigation setup in our native iOS app as well, because of — guess what — performance reasons. We ended up with something really similar with react-native...

(Actual) problem #2: The lack of performant, JS only listviews.

There was a time when we felt like we would never be able to provide a pleasant user experience, and we’d have to fall back to native code. Our iOS listviews are littered with hacks that optimise for cell re-usage.

Flipkart’s react-native-recyclerview helped us out in the end. It resolves the same issue we had with navigation: it only renders what it needs to.

The other tool that came in really handy is animation. Adding good looking appearance animation helped us greatly — not with actual speed, but with the perceived experience.

Our problem with the built-in components (ListView, FlatList, etc.) was that they pre-render too many items. This makes it hard / impossible to create appearance animation, and without animation, rendering these views feels too slow with react-native.

In the near future, we may see an official component that wraps UICollectionView & RecyclerView — fabric (coming this year?) brings synchronous JS<>native communication to react-native, that’ll could enable this easily.

(Actual) problem #3: Poor low-end Android performance

Yes, it’s not great. It may never be great! But, we’re fine with that.

I personally appreciate that you can do whatever you want with your Android phone (unlike on iOS, where you’re pretty much boxed in) — but as a business with a really small team, we have to have our priorities straight. Based on our stats, almost exclusively only high-end Android device owners spend money on the Play Store. We can serve them well with react-native. Our low-end device users are experiencing the slowness they’re used to if they’re opening up Google Map, etc. — we may have never had an opportunity to have a 1-to-1 Android version without the current setup. We’re serving them the best way we can, and even if Drops was built on native code, it’d take a lot of time to make it run fast on these devices. And we’ll struggle to prioritise over other features we have in the pipeline.

(Not really a) problem #4: Being boxed in

The existence of react-native-web demonstrates how universal the API is — and even if react-native will be replaced with “preact-native”, or when it’ll be easier to write code in ReasonML or Dart (with Flutter), our future codebase structurally will look very familiar to today’s.

If Facebook ever abandons the project, I’m confident that open source community can take good (or even better?) care of the react-native repo — this is already happening within the react-native-community Github organisation that offers some important, widely used components.

(Not really a) problem #5: React-native is immature

We struggled a lot in 2017 with react-native & react upgrades and the breaking changes they brought, but being completely honest, it has been pretty stable for the last 8 months. Yes, there were smaller regressions creeping into the monthly releases, but in general, it’s not missing major features (at least not that we’d have used).

When it comes to upgrading, we mostly follow what the guys at Expo are doing — they have to deal with a large number of libraries that they integrate into their SDK. When they’re upgrading the base react-native version, we do the same.

I was surprised to listen to the podcast with Airbnb — I think most of the mentioned technical issues have been resolved some time ago, or are ones that you’ll never face if you have a fully react-native app. We have only faced one known bug in production that’s coming from the framework itself, but they fixed it in the latest release.

(Not really a) problem #5: Platform differences

There are very few instances where we need to write different code for iOS and Android. Most of the react-native libraries that are wrapping native APIs are doing a great job building a universal interface — if you’re a mobile developer, you’ll be in for a nice surprise.

For us, the exceptions are in-app purchases. This may be resolved by using react-native-iap (which we haven’t switched to yet).

And what we thought we’d struggle with was relatively straightforward…

  1. We thought 60fps animations would be impossible, but at the very beginning, we realised that the Animated.Values that have a native driver are not affected by react re-renders. Try to drop some frames by blocking the JS thread, while a native animation is running — you’ll have a hard time. The Animated API is also really nice in general — much better than anything I’ve seen in the past as a native mobile developer.
  2. We have a physics engine (Matter.js) powering some of our screens. It runs 60 fps on iOS & on high-end Android devices.
  3. JS performance — iterating on 1000s of items is not an issue, mainly because of the async nature of react-native, where you can never really block the UI thread. Simple memoization (with reselect) on your Redux store will get you quite far — we never really had to do any other optimisation than that.
…so why chose react-native then?

Advantage #1: For us, React made mobile UI development fast and maintainable.

I personally never came across a better framework to think about developing UI and state than what react + Redux forces on us.

According to my experience, on mobile, every developer has their own casual, non-consistent implementation of MVC, MVVM, VIPER or sometimes a mix of these. This includes me of course.

This makes code hard to read, and to be honest, usually pretty horrific. It’s a lot harder to write non-idiomatic React + Redux code (that a linter would pass).

Opening Xcode or Android Studio feels super slow and painful when you’re forced to do it, once a month or so. As an ex-iOS developer, it’s surprising to me how bad Apple developer tools have become compared to what the JS community can offer. Re-compiling our old native Swift codebase takes more than a few minutes — this in itself has proven that our complete re-write was worthwhile.

Sometimes we’re bumping into issues where we can’t refresh the app instantly on Android, and it’s infuriating. Going back to recompiling the Swift/Java code is basically not an option after you’ve experienced how fast you can iterate with hot reload, even with a complex app.

We’re lucky with our UI design direction — we’ve been trying to create our own visual language, that’s platform independent, and this helped us greatly in moving fast. Even going full material design could make the react-native codebase effectively uniform across platforms (there are some great libraries for this).

Advantage #2: We can use a single language across mobile, web & backend.

We use Typescript, a superset of Javascript, that adds (and enforces) a typed layer on top of JS. Types are either implicit or explicit — we prefer the invisible hand helping us.

As a small, bootstrapped company, the last thing we want to build is silos. Typescript helps any of us touch both the backend and the front end easily — which we do regularly.

There are currently two libraries that’ll let you compile your react-native app to target the browser: react-native-web & react-native-dom. We’re using the former, and so far it has been working surprisingly well.

Of course, no two type systems are the same. Java is not Haskell. Typescript is not Haskell either, but it provides a good compromise — it was very rare that I wanted to use a concept that wasn’t at my disposal with Typescript. And did I mention compiling TS is at least 10x faster than Java / Swift?

Advantage #3: We share the vast majority of the code.

We have less than 2% of our code that’s different between iOS and Android. The jury is still out as to what kind of overlap can we achieve between react-native and web, but it looks very promising.

Advantage #4: We only need to optimize once.

You have to factor in a lot of quirks when you develop with the native iOS / Android SDKs. Our original Swift codebase is full of hacks that UIKit forces upon us. React-native almost completely abstracts this away. And 99% of the time, you only need to optimise your (JS) code once — because it’ll have the same effect on both platforms.

Optimization usually mean you’re transforming code that was previously readable to something that’s mostly incomprehensible for a human, so it runs faster on the machine. The good part of optimising a React + Redux codebase is that most of time you’ll be adding simply memoization, which does not fundamentally makes your code harder to read, and it automatically deals with the infamous “cache validation” issue 😱.

Advantage #5: You can always use native code if you want to.

What happens when you hit the limits of react-native? You write a native component. Having this ultimate backup plan is very comforting. Yes, we have one native component. (That’s not a lot!)

Verdict

React-native provides us a more appropriate abstraction level for developing mobile UI. This is what matters the most — we’re optimising for a small, super-capable team, and not writing and architecting the same code twice makes a huge difference. On top of that, the widespread support, rich JS ecosystem and the speed we can iterate with almost immediate compile times and hot reload makes this framework a clear winner over the current alternatives.

Yes, you’ll face issues with a react-native app. Like navigation, slowly loading listviews, etc — exactly the same things we had issues with when writing native code.

If you have mobile devs, who don’t like change, they’ll be resistant to it. If you have a large native development team, and a million LOC, integrating react-native may fail and may not have much advantage.

But if you start on a greenfield project, there is very little downside of starting using react-native now. And you hear this from a technical co-founder, who has a a company on the line here.

After you figure out how to overcome the initial hurdles, you’ll enjoy developer productivity that’s unprecedented in the mobile world. If you like that sort of thing!

Would I have thought 3 years ago that we end up building a mobile tech stack on Microsoft’s & Facebook’s open source code, wishing that Google’s and Apple’s clunky and slow middle layer would disappear? No way.