At Witworks we have been developing the Blink smartwatch, which runs on a customised version of Android called Marvin. We have also been building companion Android and iOS apps, for the smartwatch, in React Native (RN). If you're evaluating React Native for your next project, or just curious about it - here is my story. In this article, I will share our overall experience with RN, and the things we have learned so far.
Right about a year back, we have started the development for the Blink smartwatch, and I had taken the responsibility of making the companion mobile apps. Apart from just syncing the data between the watch and server, our Blink mobile apps have lots of features; such as the app store, music player, timeline, watch face store, and more.
Our team was on the verge with the decision to develop native apps for both platforms, but luckily we got to know about React Native which promised native look, feel and speed. We gave it a try, and to be honest we couldn't feel any difference between the native app and the React Native app built by our team.
P.S. We are using Swift for iOS, and Java for Android; for the development of background tasks.
Let me highlight a few things that we have come to like the most about React Native.
Hot reloading & Live reloading
React Native also has a feature called Hot Reloading. It keeps the app running, and injects the new changes in realtime. At this time, hot reloading is not working perfectly in a few situations, but a reload of the whole app, usually fixes it.
Just like we can debug the elements on the web, we can also debug the elements in React Native. There is an inbuilt inspector inside the dev menu, which is very useful for UI debugging.
Currently, Chrome debugging can be used only for accessing the console. As of now, the 'React' tab does not work with React Native. But, there are on going discussions to make it work. Meanwhile, you can use Nuclide's "React Native Inspector" as a workaround.
Read more about it here.
Click to Tweet
Stylesheet with Flexbox
Layouts in React Native are implemented with CSS-like stylesheets which allow you to customize your components' properties such as border, height, width, color etc. The amazing thing about it is that a component can specify the layout of the children using flexbox, and we don't have to care about the browser compatibility.
Flexbox works the same way in React Native as it does in CSS on the web, with a few exceptions. The defaults are different, with flexDirection defaulting to column instead of row, and alignItems defaulting to stretch instead of flex-start, and the flex parameter only supports a single number.
Easy to expose Native API
We have implemented custom data transactors in Java, and Swift for the communication between our watch and the mobile phone and have exposed the APIs for these data transactors to React Native. It has also been very easy to expose the Native modules and Native UI components to RN.
P.S. I have also written a small story on Hashnode about how I built a Custom Android Module for React Native.
When we push an update to Android/iOS, it goes through the corresponding approval processes. In Android, it usually takes around 24 hours to reach users' phones. In iOS, it takes around 3/4 days. As a result, if we want to push an update after fixing a bug, we usually have to wait for 3 to 4 days to send a fix to the users.
However, things are different with React Native. Using Codepush or AppHub, you can easily push instantaneous updates to the users' devices. Your users don't have to do a thing to get the updated version of the app. When we were testing our app with our beta testers, we used to get lots of bug reports. Codepush helped us send updates in a timely manner.
React Native's community is one of the best open source communities. There is also a public Facebook group called React Native Community which helped me a lot when I faced a few certain, weird bugs while developing the app.
There are of course many more features in React Native that we love, but the ones above are the most important ones. Now, let me quickly highlight the top 3 pain points which we faced.
Animation APIs run all the processes on the JS Thread. Consequently, we had faced some performance issues while using these APIs. This is one of the most upvoted issues on Product Pains : Offload some animations from JS thread for better perf. React Native community is slowly moving many animations to native, and the performance is much better than before.
Frequent Release cycles
React Native has a two-week release cycle. There are many features, and bug fixes in each release. But every release, often brings breaking changes as well (maybe in a “move fast and break things” spirit specific to Facebook). In these cases, we have to spend a lot of time fixing things while upgrading the code.
Lack of Resources
When we started a year back, there weren't enough resources to know about many of the hidden features of RN. Even the documentation was not proper. Getting the answers to React Native questions was very difficult. But now the community has grown much bigger, and the documentation is getting better every day.
How we tackled the performance issues
While building the companion apps, we came across some performance issues. Eventually we managed to fix all of these issues, but the following ones are worth mentioning :
We had a use case where we had to show all the songs from a phone in a ListView. A few of the phones we tested had more than 3000 songs. It was taking a lot of time to load all of the cells while using the ListView. So, we tried to load the songs whenever users reached the end, or 20 cells before the end. But, we faced two major hurdles :
- It took lots of memory if users scrolled through all the 3000 songs.
Soon after that we started experimenting with SGListView, which is a memory minded implementation of React Native's ListView. It reduced the memory footprint, but in our case the rendering was nowhere close to native apps. We decided that longer lists couldn't be done in React Native and made this particular part in Swift and Android. In the meantime, RN team was making a lot of changes to improve the performance (Now it has improved a lot, but is still not comparable to native performance, IMO). Nevertheless, I was keeping an eye on all the ListView related implementations by React Native community back then, and I came across two interesting implementations :
Actually, Asksonov built two kinds of Lists in the Repo. One is exactly like Tal Kol's implementation. In another one, instead of using the bridge heavily, he made the styling in UITableView itself and exposed a minimal API to customise the design. This offered amazing performance without having to write a single line of Swift or Objective-C code. However, if you want to make major modifications to the UI, then you would have to write Swift or Objective-C code.
Eventually, we made our own implementation of ListView for our use case which is based on Aksonov's Implementation.
Slow page transition
InteractionManager. While making the transition, we load lesser content and render the remaining content, once the transition is complete. It helped us provide a smoother experience to our users.
JS Thread stalls on animating the image size
We noticed a frame drop while trying to animate the image size. Soon we got to know that the image was re-cropped and scaled from the original image when we were trying to adjust the size of the image. Transforming the image using scale property fixed the issue for us.
Out of Memory Exceptions
iOS is a lot more forgiving than Android, with memory usage. Initially, our application was suffering a lot of memory issues, which often lead to
OutOfMemoryException in Android. Eventually, we experimented a bit, and reduced the size of the images as much as possible, to get rid of this error. React Native Android is based on Fresco for loading and displaying images. Downsampling is disabled for PNGs in React Native, since it is still experimental. So, we have started using JPG images whenever possible.
Lastly, I have got the following 5 tips for anyone who is evaluating React Native:
Start by implementing everything in JS for maximum productivity.
If traditional React optimizations fail, surgically move the troublesome parts to the native part for better performance. But, don't overuse the bridge.
For animations/interactions, try to use declarative libraries like
Animatedwhenever possible. However, some complex interactions can't be expressed declaratively, and offloaded.
Use JPEG images whenever possible.
Investing time on learning more about the React Native internals will be very helpful in making better apps.