Last year, I worked on a project that we had hoped would just be a mobile-savvy web page, but for various reasons we ended up needing to build native iOS and Android apps. I was still getting the hang of the new Swift tools for iOS, and had never done any Android development before - but I had heard interesting things about React Native, so I figured I would give it a try. While React Native certainly had its headaches, it was still much easier to work with one single codebase in a fairly familiar language rather than having to maintain multiple codebases and learn multiple new languages and development stacks.
Over the last few weeks I worked on a spin-off from last year's project, and it got me a bit deeper into React Native - but I also ended up dealing with a ton of headaches getting things configured and set up properly. For a while I ended up with TWO different React Native projects, one which would build and run properly only in iOS, and another which would build and run properly only in Android. After some headaches writing shell scripts to copy all my application code and resources back and forth, I finally figured out a way to build a React Native app which actually worked on both platforms.
This post is a way of sharing what I learned from my latest React Native adventure. I spent a lot of time poking through Stack Overflow and GitHub issues pages and various blog posts and articles, and ended up chasing a lot of dead-ends. There was also a lot of shouting and swearing. I hope this can spare you some of that.
I want to note that I am definitely not a React Native expert. I just wanted to share my experiences and how I got around some of the gotchas that I faced in this development process. If this series helps anyone, I'll be happy. If it just makes some people laugh, I'm fine with that too.
I want to point out though that React Native changes really really quickly - especially considering all the extra libraries and package managers it has to work with, which are all changing all the time. React Native is like a river - you never step in the same thing twice. So these notes are up to date as of mid September 2019. If you're reading this in 2020, then you probably have all sorts of new problems to deal with which I didn't cover here.
One reason to stick with Expo is if you have trouble installing or running XCode and/or Android Studio. Expo will let you at least build stuff and see what it looks like - but getting a real deployment working at the end of the day will probably be more problematic with Expo (at least it is right now).
So, if you're looking at that Getting Started guide, make sure to click out of the "Expo CLI Quickstart" and go to "React Native CLI Quickstart". For the rest of this, we're going to skip Expo and just work in regular old React Native. You'll thank me later.
> /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
> brew install Yarn
> brew install node
> brew install watchman
(Watchman is supposed to look for changes in your files and automatically trigger events, but for whatever reason I could never get it to work properly. You can also just reload things manually, which is probably cleaner anyhow.)
> brew tap AdoptOpenJDF/openjdk
> brew cask install adoptopenjdk8
> npm install -g react-native-cli
This is the command-line tools for React Native. Obviously important. You should already have NPM installed from the earlier steps, but otherwise you might need to do it yourself:
> curl -0 -L https://npmjs.org/install.sh | sudo sh
Then you need to install XCode. This is like a DVD worth of downloads, so go have a snack or something. Don't forget to also go into XCode's preferences and pick Command Line Tools from the "Locations" tab.
Then you need to install Android Studio. This is also multiple gigabytes of downloads. Make sure you've picked "Android SDK", "Android SDK Platform", "Performance (Intel HAXM)", and "Android Virtual Device". On the welcome screen, pick "Configure" and select "SDK Manager". You need to select "Android 9 (Pie)" SDK, but you can also choose a later one too ("Q" is the latest). Make sure you've picked "Android SDK Platform 28" and "Intel x86 Atom_64 System Image". Click "Apply" and go for another snack while it downloads everything.
Next you get to add to your $PATH shell variable. $PATH has traditionally been a big headache for Java development, but these updates seemed to work okay out of the box. Go to your home directory and open .bash_profile or .bashrc and add these somewhere:
If you're not using the Bash shell then you'll need to figure out where your config lives. This is the kind of fiddly details that Expo deals with, which is great - but it adds other problems down the line.
Okay now we're ready to build our application! In your terminal, go to the directory where you want your project to live, and type:
> react-native init [appname]
Let's give our example app a proper name. Let's say "Picselect" - it'll be a simple app for selecting from a collection of pictures. So in this case let's type:
> react-native init picselect
Now you're ready to run your apps!
The init process should have created a "picselect" directory. It's likely to be more than half a gigabyte in size (!) - so keep that in mind if you're building lots of test projects, since you might start to run low on storage space. Go into that directory and check things out. You'll see a "node_modules" folder - full of all sorts of stuff including virtual machines and various library type things. You'll also see folders for "ios" and "android" - these are standalone apps for each platform. It should be ready for you to take a look.
Now you can run the project in Android too! Unless you have lots of RAM on your computer, you'll probably want to quit the iOS simulator and XCode. If you want to warm up your office, feel free to have both environments and simulators up and running.
Open up Android Studio, and just like in XCode you don't want to open the project the obvious way: don't pick "Open an existing Android Studio project", instead, pick "Import project (Gradle, Eclipse ADT, etc.)" and then select the "android" directory in your app. This does some extra cleanup and reconfiguring things and cuts down on some potential problems.
Android Studio will churn for a while building the project from its "Gradle" build configuration. There's a menu in the top toolbar for picking a simulator to run. If you don't have one yet, you can select "Open AVD Manager" and pick "+ Create Virtual Device" to set up a new simulator device. Pick a recent phone. I have a "Pixel 3a XL API 28" as my main device. It will then download about a gigabyte of simulator files but after that you'll be ready. Once you're back on the main screen, make sure your AVD has been selected in the menu and then click on the green triangle to run the app in the simulator. This will take a while, so go get another snack.
You might get a message on the simulator saying something like "Unable to load script. Make sure you're running a Metro server" - this might happen if you closed the terminal window that came up when running the iOS simulator, or if you've simply started with Android Studio first. XCode will automatically launch the server, but Android Studio won't. Open a terminal window, go to the project directory, and type
> react-native start
This fires up the code server. If you want to see something fun and/or terrifying, poke around the Android Studio interface until you find the "Logcat" viewer, and watch all the error messages roll by. After your iOS experience, you should be able to just take this in stride, like it's a green-screen from the Matrix.
You might get a message that Android Studio would like to upgrade your Gradle plugin. I don't tend to do this, at least in the middle of a project, since even a small point difference in any dependency can cause errors that bring the whole project down.
The Metro Bundler terminal window will show the project JS being built and bundled and sent over to the simulator environment. If everything goes well, you should see the "Welcome to React" screen like you had on iOS. One JS page, two native apps in two totally different environments. Pretty cool eh?
If you got this far without any crashes or configuration explosions, congratulations! You now have a React Native app running in both Android and iOS.
Now that it's working you need to get a snapshot of all this so you can return to this state any time. For this you'll probably want to use git. You may already have git installed. If not, follow the instructions here. If you're doing any coding at all, git is a super useful tool - it's like a time machine for your code.
Anyhow, back on the command line in the root directory of your project, type:
> git init
React Native helpfully comes with a properly configured .gitignore file, which means it won't be tracking all of your temp and build files or your massive node_modules folder. Type:
> git add .
> git commit -m "Starting version that works"
Taking a snapshot in your version control every time you have something running properly is a valuable practice in React Native development. It's common to have some small configuration change bring everything to its knees, and it's easier to simply roll back to a version that you know worked than to hunt down whatever caused the problem.
Now you should hopefully have an app that works and a way to get back to that state through version control. Next we'll make some more important adjustments and actually start writing some code.