React Native: Separate Bundles for the Simulator and Real Devices

When building applications with React Native, you need to switch between development assets (served from development server) and offline assets (built along with the application archive). The React Native documentation provides one way of doing this, but it is manual and error prone. In this post, I will suggest another way which is more automated.

React Native’s Suggested Way:

To run your React Native app on a real device, you have two options:

  1. Point the application at the external IP of your dev server
  2. Build the app using an offline bundle.

You probably don’t intend for the App Store version of your app to talk to your dev server. You can get a more realistic test of how your app will behave once deployed by testing an offline bundle.

I find that it is clunky to use the suggested method for building an offline bundle. The docs tell you to comment out the dev server bundle and uncomment the offline bundle. To switch back to a build suitable for the simulator, you need to uncomment the dev server bundle and comment out the offline bundle. Here’s the original generated code:

  /**
   * Loading JavaScript code - uncomment the one you want.
   *
   * OPTION 1
   * Load from development server. Start the server from the repository root:
   *
   * $ npm start
   *
   * To run on device, change `localhost` to the IP address of your computer
   * (you can get this by typing `ifconfig` into the terminal and selecting the
   * `inet` value under `en0:`) and make sure your computer and iOS device are
   * on the same Wi-Fi network.
   */

  jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/index.ios.bundle?platform=ios&dev=true"];

  /**
   * OPTION 2
   * Load from pre-bundled file on disk. The static bundle is automatically
   * generated by the "Bundle React Native code and images" build step when
   * running the project on an actual device or running the project on the
   * simulator in the "Release" build configuration.
   */

//   jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];

Downsides to the Default Way

This is manual and error-prone. If you forget to switch your comments, you could archive and deploy a broken build to Test Flight. It also introduces needless changes to your git diff if you forget to reset before checking in your code.

A Better Way

First, I change AppDelegate.m so it conditionally loads either the dev server or offline bundle based on the RCT_DEBUG flag:

if(RCT_DEBUG == 1) {
    jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/index.ios.bundle?platform=ios&dev=true"];
  } else {
    jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
  }

React Native setsRCT_DEBUG to 1 if the build is a “Debug” build. It sets RCT_DEBUG to 0 for a “Release” build.

Then, I set up a second build scheme for my app. The default scheme creates a debug build, so I set up a new one for release deployments called “Release – Name of App.” I edit the scheme and set “Build Configuration” to “Release” and uncheck “Debug executable.”

To build a debuggable version for the iOS simulator, I use my Debug scheme. To build a version for a real device or deployment to Test Flight, I pick my Release build scheme.

I also set the “Archive” action of both schemes to “Release” Build Configuration. This means I can’t accidentally make an archive for deployment that targets my dev server.

I find this to be much less error-prone that the suggested way. Let me know what you think.

Revelry offers executive advising, design sprints, and custom software development.

Check out some of the things we’ve built at Revelry.
Book office hours here.