The resurrection of a year-old React-Native app

I got an upgrade project. React Native application which has not been released for a year with the last commit on 29/02/2020.

The task, as simple as it might sound, is a challenging one—get the app compiling for Andriod and upgrade it to the newest (0.63.4) React Native version.

You guessed it right—the app does not build. The team who inherited the app from another team are full-stackers but after spending a few days on it decided their time is better used in other parts of the projects and outsourced the compile-fix and upgrade to yours truly.

Lets crack on it.

As a first step, naturally, I git-cloned the repository into my main-workstation, installed dependencies and tried to run it:

$ npm install 
$ react-native run-android

As expected, a bunch of errors. I could have started working on errors in my main-workstation, however, there’s a big gotcha with React Native environments—they are hard to isolate.

I have a lot of different projects in play which ranges in versions for php, node, python and other supporting toolings. With docker, these are easy to isolate project environments, meaning all projects sit in the same machine but run in isolation.

React Native is challenging to isolate and I’m yet to find a good docker container which can support 1+N React Native projects with different project toolings (node, java) whit out sacrificing speed of development. That's right, people highly underestimate how much admin-time it takes to deal with docker.

As a result, I run React Native in the OS. I was certain my main-workstation will be to new for the year-old application, and I did not want to change anything in my OS setup.

It’s good that exactly for such purpose I have a spare MacBook which I factory-reset and setup project by project.

Fresh macOS Big Sur, install brew, environment toolings and let’s take a closer look at the repository.

# Get brew in, then...$ brew install --cask android-studio
$ brew install --cask android-platform-tools
$ brew install --cask adoptopenjdk
$ brew install react-native-cli
$ brew install nvm
$ brew install yarn
$ xcode-select --install
# Optional
$ brew install alacritty
$ brew install zsh

We do not know if the code base is a subject to a specific node version, as a result, we want to be able to change node version easily. I use nvm for this but n works great too.

I had an error running yarn install as the repository was sourcing a private repo in the package.json. I asked the team to add my id_rsa.pub and the problem was gone.

The package.json had a useful script "android": "react-native run-android" which meant I can run for android with yarn android.

$ yarn androiderror Failed to launch emulator. Reason: No emulators found as an output of `emulator -list-avds`.

A self-explaining issue—launch AVD or connect up a device. I chose to launch AVD which is pre-created when you install Android Studio. It was Pixel_3a_API_30_x86. Launch AVD, run for android.

$ yarn androiderror React Native CLI uses autolinking for native dependencies, but the following modules are linked manually:
- @merryjs/photo-viewer (to unlink run: "react-native unlink @merryjs/photo-viewer")

I was (almost) sure this is not an issue. Syncing Gradle via Android Studio conveniently avoids double links (and notifies you about that) done by not removing historical manually set links, however wanting to have the build screen error-free I removed it.

$ react-native unlink @merryjs/photo-viewer
$ yarn androidNo variants found for 'app'. Check build files to ensure at least one variant exists.

I expected this error. As it is a fresh device and the application is a year old it is expected that needed API will be missing.

Open build.gradle and check what SDK do we need, it was 28. Let’s add it Android Studio > Preferences > Android SDK

$ yarn androidCould not initialize class org.codehaus.groovy.reflection.ReflectionCache

The issue is caused by current Java (15.x) and an old Gradle—3.4.2. I did not want to start changing code, I chose to downgrade the Java version.

I looked at the git-history to determinate what time application was created, it was the beginning of 2019. Based on the Java version history, I chose 11 as the most likely version which was used to develop the app and amended my environment.

$ brew uninstall --cask adoptopenjdk$ brew tap AdoptOpenJDK/openjdk
$ brew install --cask adoptopenjdk11
$ java --version
openjdk 11.0.10 2021-01-19
OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.10+9)
OpenJDK 64-Bit Server VM AdoptOpenJDK (build 11.0.10+9, mixed mode)
$ yarn androidExecution failed for task ':app:compileDebugJavaWithJavac'.~/MainApplication.java:60: error: cannot find symbol
packages.add(new MerryPhotoViewPackage());

The issue points to the package we previously unlinked, git status reveals modified files:

modified:   android/app/build.gradle
modified: MainApplication.java
modified: android/settings.gradle

Let’s restore them and try again.

$ yarn androiderror React Native CLI uses autolinking for native dependencies, but the following modules are linked manually:
- @merryjs/photo-viewer (to unlink run: "react-native unlink @merryjs/photo-viewer")

We get the linking error (rather, a warning) again but the build process runs passed that just fine. Ignoring.

Great, we are in the Metro Bundler now!

An odd t.isPlaceholder is not a function issue.

It originates at Babel. A quick look at the repository revealed that I missed removing yarn.lock before doing yarn install.

If the shape of node_modules are changed by yarn, yarn.lock must also be changed, otherwise, it will throw issues.

$ rm -rf node_modules
$ rm -f package-lock.json yarn.lock
$ yarn install

At this point, I got the app compiling but it crashed the moment it finished building. Metro Bundler reported no issue.

Android Studio Logcat showed the following crash report.

couldn't fid DSO to load: libhermes.so caused by: APK was built for a different platform

A further code investigation showed that the application is indeed using hermes and the code seems to be in accordance with 0.61.2 upgrade notes.

A deep look into this issue revealed a few great reads with a handful of potential fixes to try.

I was not convinced I needed any of them — did not make sense. I decided to take a closer look at the React Native repository and the build.gradle configuration for my version (0.61.2). The soloader version is 0.6.0 , the application code is using 0.8.0. One of the suggestions in the resource above was changing soloader to 0.8.2. I tried both, 0.6.0 and 0.8.2 and both worked.

It seems 0.61.2 does not play with soloader 0.8.0 . I could have disabled hermes and tried with jsc, however, I wanted to get the app compiling with no (or very little) code changes.

I had a few other issues related to nvm and zsh. These were avoidable, though, by simply not choosing these tools. Both issues were blocking the Metro Bundler terminal output, not allowing the compile progress to finish.

nvm is not compatible with the "PREFIX" variable: currently set to "/usr/local"zsh compinit: insecure directories, run compaudit for list.

I’m not sure why but unsetting default alias fixes the issue. The zsh compinit is a well-known starter-issue.

$ nvm unalias default
$ compaudit | xargs chmod g-w

Conclusion

Apart from one issue which seems to be caused by unfinished-upgrade, all challenges were environmental.

Always capture environment used to develop the application in projects readme.md, it will safe time to onboard new team members and restore it in a fresh system.