Adventures with React Native
Some considerations for you if you are thinking about using React Native.
The promise of React Native
The goal of React Native is to answer a high-tech Holy Grail: Write Once, Run Everywhere. And by and large, that is true, at least for iOS and Android which are the only platforms I used with it.
My exposure to React Native (RN, henceforth) is limited. I am not a professional RN developer and I do not know all of the ins and outs of using it. But the 6 months I spent with it were interesting. I started at a small company as the new (and only) mobile developer. I took over the RN mobile app someone else had written. They wanted some new features and to see if its performance could be improved. I was hired for my iOS and Android experience because the CTO thought the RN application should be scraped and re-written natively for iOS and Android. I just had to make some improvements before I could begin the re-write.
Their app uses a complex data set, real-time data feeds (via WebSocket), maps, lists, barcode scanning, and some other things. The data can be just a few hundred items or it could be thousands.
React Native apps are mostly written in JavaScript (the native parts are written in the platform’s code - Objective C or Java - and then bridged into the JavaScript). When you start a RN project you have RN generate the actual platform-specific application which forms a container to host your “application” (JavaScript) code. Its essentially a JavaScript interpreter that uses the native bridge to get components to appear and input to be received. For all that it does, its actually quite fast. But remember, your application’s code is not being run directly on the hardware, it is being interpreted (well, translated to byte code, then that’s run) within a virtual machine.
In some ways, React Native is like Android itself, which runs on a variety of processors. Your one Android application (in Kotlin or Java) is executed in a Java Virtual Machine which as been built specifically for the hardware of the device.
The company’s app’s performance problem stemmed from the very fact of RN’s existence: the code is not run optimally for the hardware. Imagine downloading 2,000 items and then rendering them onto the map. Then moving the map around, zooming in and out, etc. Very very slow. Plus there were lots of graphics going on. It was just a performance mess.
Getting Started
I mentioned above that when you start a React Native app you have RN generate the application shell - the actual native application which runs on the device and hosts your JavaScript app code. That’s true, but there’s more to the story.
When you write an iOS or an Android app, you start off by getting the respective IDE: Xcode for iOS and Android Studio for Android. That’s it. Download those, make a quick test app, run it, and start building. You may need some more complex software that you can purchase or find for free (like a barcode scanner) if you do not want to take the time to build it yourself (which, in these environments, is always an option).
When you want to start off with React Native you need a small army of new programs: brew
, npm
, node.js
to name a few. That plus React Native itself. If you just recently upgraded your macOS, you may find the versions of these haven’t quite been upgraded to match, but generally these are pretty solid.
Once you get React Native to generate its shell program you’ll find your directory divided into two parts: one for iOS and one for Android. Inside the iOS directory is a real iOS workspace and project. Go ahead and open that. What you’ll find will be a surprise. This is a very large app (after all, its a full blown advanced JavaScript interpreter with code to host and bridge to native modules). But what I found the most surprising, and the first disappointment, were the nearly 900 compiler warnings. Most deprecation warnings, but also syntactic irregularities. And it was all Objective C. It made me wonder if anyone was actually working on this app full time to remove those because, to me, compiler warnings are not show stoppers, but they should be addressed. I like delivering apps without warnings if at all possible.
True or False
When you build a RN application you run into a lot of things you need. For example, we needed a barcode scanner and access to the camera for both the barcode and so the user could take photos. After doing some digging I discovered this entire world of React Native developers. Basically, if you need to do something in your React Native app, someone has probably already written a plugin to do that. For this article, I’ll focus on the barcode scanner module, but what I’ll describe happened with a couple of modules.
I found a couple of barcode scanner modules for React Native. One thing to look for is the platforms they cover; sometimes the author only supports one platform. When I found one that looked promising I created a new separate RN app to test it out. Good modules come with complete instructions: how to incorporate the module into your code (for Android, change the gradle.build
file, for iOS change the project info.plist
for instance or some are even simpler). Some modules leave it up to the interested reader to figure out the installation.
I cannot stress enough how grateful I am for stackoverflow.com
; a troupe of heroes.
Once I got the module running in a test application and understood how to use it, I replicated the installation into the main application and hooked it up. That worked for the most part EXCEPT in this situation: The company’s app already existed which means its React Native directory/codebase was generated months earlier before I started - it was all in git
for me to just clone locally. My test RN app was recently created. I was using a newer version of a RN app in my test than was being used in the company’s app. In other words, version incompatibilities.
I had to tinker with the code from the barcode scanner module to make it compatible with the RN application code for the main app. Most 3rd party apps come with their source code, but it was the wrapper code to fit it into the RN environment that was the cause of the problem.
This brings me to the actual heart of this article: upgrading.
Upgrading
During the course of my involvement with the company’s original RN app - and early on in the involvement - I accidentally let my computer upgrade Xcode (from 9 to 10 at the time). I didn’t think much of that until I tried to run the app. This began a journey into a land of frustration like no other I have experienced in my years of writing software. Were I a better writer, I could craft for you an Orwellian tale to make you lose a few nights of sleep. But I am not, so I will keep this as brief as possible.
When Xcode upgraded it also upgraded the iOS SDK which brought with it some deprecations but also a few out and out changes - you know, when the deprecated code is finally dropped.
The React Native version used to build the app used some of that now defunct code. Which meant it failed to build. After searching on stackoverflow.com
I found a couple of workarounds: update to a newer version of React Native or go into your RN code and change it. Now keep in mind that this is code that is generated and could be replaced at any time should you need to rebuild the directories; you really should not need to edit it.
I was able to make the changes to the RN code and move on. But that only lasted a few minutes because several of the many (many) 3rd Party modules used to build the app also had similar problems. One fix for one of them was to just get the newest version of that module. In my naiveté I did that.
This started a chain reaction of updating modules because module A now didn’t like the dependency on module B which was now incompatible with iOS - I had to upgrade React Native. To do this you change a few control files, erase all of the modules and re-get them (ie, all that work trying to upgrade them by hand goes out the window) and then re-generate the React Native shell application.
Your React Native app may turn out to be highly dependent on community software, written and, hopefully, maintained by people who are often not being paid full time to write these modules. They may have written them to do their own work and donated them or are just hobbyists.
Now as it happens, some of the 3rd party RN plugin modules were no longer being supported and thus, not compatible with the new React Native code. So while some modules were just fine, others were non-functional. For those I had to find newer replacements (recurse to the above section).
The bottom line here: Upgrading your OS or IDE can open a days-long can of worms with React Native. You find 3rd Party modules cannot be upgraded or they are not supported or the authors have intentions to update too, but just don’t have the time yet to do that.
Hello Simulator? It’s Me
Earlier I mentioned you run your app on device simulators (or emulators for Android). You can also run them on real devices. This is all great - when it works. Remember that upgrade process? Well, iOS upgrades also change the simulators. More important - they change the location or name of the simulators. When you go to run your RN app, there are scripts that run which hunt down the simulator you want and launch it. When the Xcode 10 update came through, the names of the iOS simulators changed and even the latest version of RN that I upgraded to had no clue where these simulators were.
So again, back to stackoverflow.com
for the answer: edit the RN execution script that finds the simulators and change it so it detects the simulators. And that was fine for a while until another update to Xcode ended that and I had to go back and change the script again. Keep in mind that should I have had to update React Native again, I would have had to change this script.
Debugging
When you use an IDE on a native application, you just set a break point right in the code and launch the app. The app runs until it hits a break point then you can examine the current state, etc.
You cannot easily do this with React Native. And of course, there are several ways to get debugging to work. The easiest one I found was to open Chrome
and attach it to the JVM running inside the iOS simulator (I could not get it work with an Android emulator). You could then set a break point in the JavaScript code and most of the time it would work. Sometimes (like 40% of the time) it would break inside minified code which is always fun.
There is a React program you can use but I never had much luck with that. I think it was more suited to web-based React than React Native.
The starting up of the debugger (you had to get the incantation just right or it would not work), the attachment to the process, the running of the React Native mobile environment, hoping your break points were still there (often you have to reset them), was just another frustration.
Thoughts
At this point you are probably thinking I am not a fan of React Native. And you’d be mostly correct. Following my experience with RN, I got to re-write the company’s app natively for iOS and Android. For Android I chose Kotlin rather than Java and for iOS I went with SwiftUI since it was just out and looked super cool - PLUS - it is a reactive framework and that is one thing I really liked about React. In fact, I liked the state flow so much I used a Kotlin version - ReKotlin
- in the Android app.
Oh - and I stuck with iOS for this blog post, but I also had similar issues when it came to Android and updates to gradle
.
The promise of React Native: Write Once, Run Everywhere, holds true to a point. And that point is upgrading. Once your app has been written for a while, along comes an OS update and an IDE update. This can send you into a tailspin. You have React Native core code that becomes unusable. You have 3rd Party modules that are incompatible with newer version of RN itself. At one point I thought it was hopeless because it was a vicious cycle of changes.
My final take away for you is this: if you use React Native, understand that for long haul, its an investment. I see the appeal as I wrote the same app twice for different platforms. If there is a problem I may have to fix it twice. But the upgrades are easier and the app is way, way, faster. I like Kotlin and Swift far more than I like JavaScript, which is just a personal preference here. So for me, the tradeoff of a single code base versus multiple code bases is worth the effort. Bonus if your company has both iOS and Android developers!