Building React Native Apps
We’ve been using and loving React Native as noted in my previous post. As we are working towards rolling out a fully-featured app, one thing that needed solved was how we should build the app for different environments. For example, how can we make (slightly different) development, staging, and production builds?
In a Github issue, I ran into a few other people also wondering how to do this, so I’ve added a few ways to the Example App to show the approaches we are using.
The three approaches we are trying out are:
- Configurations
- Compile Flags
- Run Variables
Environments
I had already added the Environment
model and EnvironmentStore
store to the project. However, I just added actual dynamic configurations to the XCode project and the EnvironmentManager
code is using that.
Here is the staging config:
1 2 3 |
|
That gets used by the EnvironmentManager
to send that, along with other data, over to JS land.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
|
By naming a scheme, that uses this config, we can launch the app knowing that its world is slightly different as determined by the Environment
model. For example:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
By the way, I also added some of the other information from Objective-C like the version, build code, locale, and whether we’re running in the simulator. We’ve found a use for all of those in our Javascript code.
Configurations
This new build is using the kEnvironment
from our custom xcconfig
files as seen above. So we can pass in the environment name via configuration.
XCode has these schemes that set up the configurations. The issue I wrote up was lamenting the fact that all the of the child projects (like React Native) have the use the same name (“Debug” or “Release”) for it to work as expected. For example, I can’t really have a configuration called “Staging” that gets all the good stuff from the “Debug” configurations.
I’ve more or less just accepted this and moved on. Our “Staging” and “Production” configurations just end up using the default (“Release”) configurations from all the children. That’s working well enough. The the other two approaches are ways to mitigate this issue, though. So when I said I accepted it, I guess that’s not quite true.
As a side-note, I’ve now realized one piece of magic that CocoaPods has. It does all this stuff for you somehow and that’s probably why there is a different configuration that it makes for each of mine. Should React Native be on CocoaPods? I don’t know.
Compile Flags
But I want the “test” build to run in “Debug” mode. Or maybe I need to debug the “Staging” build on the phone. In these cases, I’ve shown how are are compiling the app via the command line. This allows us to define extra, non-configuration variables. Therefore, we can use the regular ones like “Debug.”
There is a new Compiler
class that pulls it all together. it basically uses xcodebuild
to compile it and adds extra info like TEST_ENVIRONMENT=1
, which the EnvironmentManager
can then use to override the environment name.
It also uses the ios-deploy
tool to put it on the phone if you ask it to do so. Try this out: npm run install:staging
Run Variables
When setting up schemes, I found that I could pass environment variables in the “Run” section of “Edit Scheme.” Then I’m using this to allow a “staging” name even though I’m in running the “Debug” configuration. This is then available as seen in the [[NSProcessInfo processInfo] environment]
code above.
However, there is a fatal flaw. This is a run-time argument that is only used once. You lose that data if you launch the app again. It is, however, the best way that I’ve found to debug the “Staging” build in XCode.
Auto-Compile
So now there are lots of ways to launch and run this app, but I kept forgetting to bundle the new Javascript code when launching it from XCode onto the phone. There’s nothing worse than working on something and realizing 10 minutes later, the code on the phone is not the newest build.
The Compiler
class does this automatically, but I looked for a way to automate the instructions in the AppDelegate
. It wants you to run with localhost when in the simulator and run react-native bundle --minify
when putting it on the phone.
So let’s automatically make those decisions based on the target runtime:
1 2 3 4 5 |
|
This automatically causes it to use the bundle when running on the phone, but we still need to remember to compile it. So I added a “Run Script” build phase to do the bundle command:
1 2 3 4 |
|
This assumes that you are using nvm.
Now it’s impossible to forget and everything is always based on the target. Nice.
Summary
I’ve updated the code of the Example App to have a few ways to build a React Native app with environment nuances. We’re mainly using the Configurations approach but the others have come in handy a few times. I hope that is helpful.