I recently finished my first React Native project. Overall, React Native was a huge win, but it was not all sunshine and roses.
Back in 2015, we built an iOS-only proof-of-concept for this application using Swift. I liked Swift a lot more than I expected, though there were some things I missed from more dynamic languages like Ruby and Smalltalk.
The proof-of-concept app had the main features that we needed, but we didn’t need to solve the harder problems at that time.
A year later, the client came back to us. The proof-of-concept had done its job and it was time to take the app further, including adding an Android version and solving some of the harder problems.
This round of development resulted in a marketing demo version of the application, so it’s not yet available in any of the app stores.
I can’t talk too much about what the app does, as it has not yet been released.
In general terms, the main part of the application is a dashboard screen with a completely dynamic set of components arranged in a customizable layout. In the proof-of-concept app, we just hard-coded a couple of these dashboards. The marketing demo version had to solve the dynamic layout problem.
The app has drawers on the left and right that contain some small list views and a few actions to perform.
There is also a wizard-like interface to handle an onboarding workflow.
Choosing React Native
When the client approached us for the second round of development, we had just come off of several web projects where we had used React and really loved it. We’d been hearing good things about React Native along the way.
Given that we now had to build for both iOS and Android, and that we had this dynamic dashboard layout problem to solve, we opted to switch to React Native for this round with the full support of the client.
This turned out to be a really good decision.
We spent the first month of the project re-writing the app from Swift to React Native, getting to feature parity on iOS only. Once that was done, it was time to get it running on Android.
We had never done anything with Android, so we had a bit of a learning curve. It took maybe two or three days to get all of the Android development tools installed, figure out how to use them, and get the app fully working on Android.
Less than a week!
Compare that to what it would have taken to re-write the application for Android in something like Java or Kotlin. This was a huge win for our client.
Dynamic Dashboard Layout
Another big win came when we implemented the dynamic dashboard layout. We had some rough ideas about how we might accomplish this in Swift, but it was going to take some digging and some research.
With React, this was really easy to do. It took a few hours to write the code that turned a dynamic dashboard descriptor into a proper layout on the screen, and maybe another day or two to implement all of the widgets/components that could appear in the layout.
This was the most important feature for our client, and the one they were most worried about. We were able to deliver it in less than a week using React Native.
Part way through the project, we added a developer to the project who had never done any mobile development. But, because she’d worked on several React projects, she was able to jump right in on this project and be productive.
The code was structured much like our React web projects, and we were using many of the same tools and libraries, so she felt at home right away.
But the language has come a long way lately; with the new ES2016/7/8 features, it’s quite a nice language. Add in some good libraries and tooling, and it’s a pretty productive language. I find that I can write really good code with it.
The React/React Native ecosystem comes with some basic tooling that makes the development workflow very efficient. React Native comes with support for both Live Reloading and Hot Reloading, so every time you save a change to a file, the app running on the simulator/emulator is immediately updated with the latest code.
One of our initial concerns about React Native was debugging. Given that we weren’t using the platform-native toolset supported by the vendor, we were worried that debugging would be a nightmare.
For debugging native code, we could still use the standard XCode/Android Studio tools, even with React Native.
Towards the end of the project, we wanted to start automating some of the tedious tasks like building and releasing versions of the application to TestFlight and/or HockeyApp.
We found Fastlane for this, and it’s a wonderful set of tools that did everything we wanted. Highly recommended, although you will spend time trying to understand how their tool names relate to the job they perform. I still haven’t got my secret decoder ring.
For all that React Native helped on this project, not everything was awesome. We definitely ran into some issues along the way.
Several parts of this application needed access to native features that weren’t part of React Native, so we had to turn to open-source native components.
We were able to find packages for everything we needed to do, but the quality and maintenance level of some of these components is all over the map. Overall, we were able to get everything working that we needed to, but not with out some struggle and swapping out libraries along the way.
React Native is a moving target. When we started the project, new releases were coming out every two weeks. Towards the end of the project, they slowed to a monthly cadence instead.
We did our best to keep up with new releases. For the most part, we didn’t run into any problems doing so. Bugs were being fixed, new features were being added, and all was well. Resources like rn-diff and later, react-native-git-upgrade, made upgrades much more manageable.
Then React Native 0.40.0 came along. It introduced a change that essentially broke every third-party native library in the ecosystem.
We were unable to upgrade React Native for quite some time while we waited for our native dependencies to adjust to this change. Remember what I said about maintenance levels being all over the map? This is where that becomes an issue. We tried working on PRs for some of these libraries, but could never get them building and running in our local environment.
Eventually, the libraries were all updated for React Native 0.40.0, but it was too late in the project for us to upgrade. Ultimately, we shipped the marketing demo using React Native 0.39 but we do have a git branch that has upgraded to 0.41.2 (the latest version available at the end of the project). We just didn’t want to release this code when there was no time left in the project to test it.
As React Native continues to stabilize, this will become less of an issue.
Our application has a few forms in it. Most are simple, single-text-field forms, but there was one form that used another kind of input widget as well.
We had a lot of trouble getting forms to work well, and we never did fully solve the problems. The single-field forms were fine, but once a form got to the point where some of the fields might be hidden by the keyboard, problems arose.
We ultimately hacked around the problem enough to get something working, but there’s gotta be a better way. We couldn’t find it in the time we had available.
This is something we’ll have to spend more time on when we come back to this project.
Yes, I listed debugging as a win above, and it mostly was. But there were a few annoyances.
React Native Debugger would often hang after live reloading. We suspect this is because of some web socket communication error. We also had issues with hot-reloading. For most of the project, it just wouldn’t work. I finally took some time to dig into it and got it partially working, but it caused React Native Debugger to hang even more often. I’ll probably write a separate post about this at some point.
Debugging layout issues is not great. React Native Debugger contains the React dev tools, and those work pretty well. You can see the React component hierarchy and look at state and props for everything.
But you can’t see the actual dimensions of the on-screen components. For that, you have to turn off debugging on the simulator/emulator, and then use the built-in React Native development tools to inspect components.
It would be very helpful if those two features were integrated into one place.
React Native’s flexbox-based layout engine is really nice, and we were able to do almost everything we wanted to do with it.
But there are some cases that iOS’ constraint-based layout engine is more flexible. In particular, there are cases where we wanted a button to be a certain distance from another widget, but that space could collapse down to some minimum distance on smaller screens. There may be a way to do this with React Native’s layout engine, but we weren’t able to figure it out at the time.
The Bottom Line
Overall, React Native was a big win for this project. We were able to achieve the two most important features (Android version and dynamic dashboard layout) with very little effort. We were able to bring on a new developer with minimal ramp-up time. And we were able to deliver quickly because of the excellent development workflow provided by the React/React Native ecosystem.
Thanks to everyone involved in making this world a reality!