Building an iOS app in React Native
I recently shipped my first React Native app, Veggies in Season. As you might have guessed from the name, its goal is to help you find out what fruits and vegetables are currently in season. This is just short recap of my experience working on it (spoiler alert: I loved it).
There are already many other excellent articles and reviews of what React Native is like, so I decided to approach this in a slightly different way. I will focus on some challenges I faced and some interesting details I discovered that I think are worth sharing. I believe this will be helpful to others getting started today with React Native.
I’ve been developing for the web for a while but I am fairly inexperienced at native development, with only a couple of semi-successful stints before. So I am fully aware that when I say that something is easier in React, experienced iOS developers reading this might say “yeah, right”.
Having said that, let’s dive right in.
Interface layout
In my previous foray into iOS development, one of the tasks I had found most challenging is laying out and styling custom (i.e. not standard UIKit) user interface elements. Coming from the web with CSS mindset, having to imperatively define the position of each element of the screen looked like a huge step back. I had also attempted using Interface Builder but I didn’t feel as productive with it either.
This is probably one of the biggest wins of React Native. It brings CSS to native development. It is not really CSS though: they have implemented part of the CSS spec in Javascript. And they have done so by only picking The Good Parts™, choosing flexbox as the standard way to lay out the user interface. If you are still not convinced by flexbox just have a look at how easily it solves some of the problems that used to be absurdly hard in CSS: http://philipwalton.github.io/solved-by-flexbox/
However, I was surprised to find out that z-index didn’t make it into the part of the spec that was implemented. I wondered what the best way was to implement a layered UI where you have component appear on top of each other.
I was not the first one to notice this, by the way. The z-index of an element is driven by the order it appears in the code so that later views are rendered on top of previous ones. For example, if you want to create a modal its view should be at the bottom of your render method. That still would not be enough to make it appear “floating above” the rest of them. For that you should set it to be absolutely positioned which luckily I found that React Native supports.
The solution was rather simple in the end but I feel like some documentation is lacking around this. And it might not be enough for some use cases where you would want to dynamically reorder views. Having a built-in API to bring a view to the front of the hierarchy or to push it to the background would be very helpful too. The React Native team is aware of this issue, so hopefully this will be less of a pain in the future.
Animations
Before I decided on actually building this app, I had been playing with the idea in Framer. You can see a rough version of the Framer prototype below:
I was curious to see how hard it would be to implement fluid native-looking animations in React Native. Animations are always challenging in a reactive, declarative framework like React since it’s hard to boil them down to a discrete number of states. React Native has two animation systems, but I will focus on the only one I used, the Animated library, which provides more fine-grained control.
Animated is very easy to use and provides a simple way to define your animations. See the example below:
Animated.timing(this.state.bgScale,
{
toValue: 25,
duration: 400,
easing: Easing.inOut(Easing.cubic)
}
).start();
This requires having initialized the bgScale state property in an special way. Behind the scenes, Animated actually uses data-binding and skips the diffing and re-rendering process typical of React. It instead does raw mutations directly over the native components using the setNativeProps API. For those interested in reading more about it, these are two good resources:
- http://animatedjs.github.io/interactive-docs/
- https://speakerdeck.com/vjeux/react-rally-animated-react-performance-toolbox
There are plenty of animation examples in the docs. I especially liked the fact that you can create a single animated value and then use interpolate to orchestrate multiple different animations based on that value.
Spring animations I had prototyped in Framer were surprisingly easy to recreate almost identically in React Native. Part of the reason might be that both projects use the same algorithm for spring physics (RK4). Framer states that clearly in the docs while in React Native you have to dig a bit deeper to find it.
Navigation
Because it was my first time using React Native, I spent a ridiculous amount of time skimming through examples and other projects to see what the best way to structure your application was.
From the two navigator components currently available I followed Facebook’s recommendation and went for the pure Javascript one, which has the big advantage of working on both iOS and Android.
It was pretty straightforward to use, but the transitions between screens didn’t feel as fluid as with the native navigator. The different available transitions were not documented yet at the time of writing this but you could find them in the source code.
I later found that you can also extend the base transition and roll your own, which I did. I created a quicker and “flatter” version of the default transition which, while still being far from perfect, feels more similar to the default in UIKit.
Debugging and other stories
Here are a bunch of tips in no special order that I hope will save other people some time:
- you can use the Chrome dev tools to debug your JS code, which is great, but the React Dev Tools extension doesn’t work at the moment even if the documentation still says otherwise.
- the best workaround for the problem above is to use Nuclide (based on the Atom text editor) which has React Dev Tools baked in.
- the “offline bundle” mode, where you prepackage your Javascript in order to run you app without having the development server running, does not work in the iOS simulator, only on the real device. This is nowhere to be found on the docs and made me pull my hair out for a while.
- the section in the docs about performance is a must-read to understand the model of main thread vs. Javascript thread and how they play together. It also has good advice on how to optimize the rendering of the ListView component, which can be slow some times.
Conclusion
I truly believe that high quality mobile apps can be built with React Native today. You don’t need to be a wizard to achieve great performance — you get that almost out of the box.
Of course, being still pretty early (at version 0.21 as of this writing) there are some rough edges. But the key for me is that none of those are fundamental problems, as many of the other so-called hybrid mobile frameworks suffered from the start. In fact, some people on the iOS development community already consider this to be a better paradigm.
I can’t say whether it is already better than UIKit. But I do know that I will be following React Native closely and will keep using it to build more mobile apps in the future.