The magical React Native Bridge (… or the Rainbow Bridge from Asgard)

When writing react native applications we benefit from the ease at which we can create unified experiences for end users on both the iOS and Android platforms. The ability to leverage React to have UI logic that we can reuse for iOS, Android, and even the browser (in many cases) greatly reduces UI development overhead and can help front end developers achieve great synergy.

Once we go beyond simple UI interactions, however, we can sometimes encounter disparate behavior between platforms that may perturb us. I’ve noticed some interesting quirks when running the same JavaScript UI code on varying environments. My contexts of comparison were iOS in debug mode vs release mode, iOS vs Android, and Android in debug mode vs release mode.

At my current job I am responsible for udpating and maintaining the FireStop Locator application on the iOS App Store and Google Play Store. We use SQLite as our local data store as it provides a reliable and performant persistence layer to store and retrieve user data, which includes projects, items, inspections, building data and various other data. SQLite provides a much snappier offline experience than using Redux/Redux persist as a JSON data store, and can hold much more data without compromising UI performance.

During the process of testing the Android app, I ran into strange behavior with SQLite. It perplexed me to see the app function perfectly fine in debug mode, whereas in release mode the SQLite queries were not returning any data. In the iOS application, I resolved this issue by keeping a node in redux to represent database transaction states, i.e. INSERT_REQUEST/SUCCESS/FAILURE or QUERY_REQUEST/SUCCESS/FAILURE.
In components that wished to render the results of these queries, I simply checked reference equality on values derived from selector functions in componentDidUpdate(). Oddly, on Android this logic wasn’t working…. but why??

via GIPHY

I suspected that the JavaScript runtime variations among environments must have been the culprit.

"When using Chrome debugging, all JavaScript code runs within Chrome itself, communicating with native code via WebSockets. Chrome uses V8 as its JavaScript engine." - excerpt from React Native docs link above

Further research into the topic beyond React Native docs seemed to highlight the idea that inconsistencies exist between the Android and iOS JavaScriptCore runtime engines. At the time of this debugging process, I was running React Native v 0.57.8. I decided I had to update the JSCore for react native on Android. GitHub - react-native-community/jsc-android-buildscripts: Script for building JavaScriptCore for Android (for React Native but not only) provided a means of doing this, but my attempt to upgrade the JSC in RN 0.57.8 wasn't straightforward and I abandoned this approach in favor of Plan B. The alternative was to upgrade to 0.59.x, which would provide additional benefits of enabling 64-bit support on Android prior to the looming August 1, 2019 deadline in the Google Play Store. The Fabric rearchitecture makes the process of replacing the JSC for another JavaScript engine much easier, so if I needed to replace the JSC after upgrading it have been a much simpler process.

It turns out the JSC that was in 0.57.8 for Android hadn't been updated in a while as compared to iOS, so the upgrade to 0.59.9 actually brought a great deal of JS parity among iOS and Android. Although the upgrade process itself wasn’t pain free, (nothing involving Gradle ever is 😪) it solved this issue and provided various performance optimizations. The app received some awesome UI performance gains: this article gives a good explanation as to the react native re-architecture with Fabric and Turbo Modules. Additionally, now our Android app should be 64 bit compatible and we even can leverage the Hooks API to clean up some of our stateful components in the future!

Heavens thrown room
Photo by Ian Stauffer / Unsplash