lastminute.com logo

Technology

React Native: there is a debug for that!

fabrizio_duroni
fabrizio duroni
mariano_patafio
mariano patafio

Learn how to leverage the power of RCTBundleURLProvider to build, run and debug on an iOS device from Xcode


In the last few days I was working in pair programming with my colleague Mariano Patafio on some new features for our React Native mobile apps. Mariano is a senior iOS and Android developer and a true ”emoji-apple Apple fag emoji-apple” (like me emoji-laughing ). At some point during our pair session we wanted to test the app on a real iOS device. The app we were working on was an existing iOS app in which we added some React Native views. If you follow the instructions contained in the React Native documentation about integrating it in an existing app, you will discover that with that setup you will not be able to run your app on a real device from Xcode. It will work just in the simulator.

I can’t believe that

It should be possible with the right setup to build, run and debug your React Native app from Xcode. I’m going to demonstrate it using the React Native example app that you can find in this github repo.

The Sample App

The app is very simple: it contains a main screen with 2 buttons that let the user open two different React Native views. Let’s see the original implementation of the app above, where we implemented a
ReactNativeBridgeDelegate that returns the localhost url of the index.bundle that contains our React Native code.

class ReactNativeBridge {
    let bridge: RCTBridge

    init() {
        bridge = RCTBridge(delegate: ReactNativeBridgeDelegate(), launchOptions: nil)
    }
}

class ReactNativeBridgeDelegate: NSObject, RCTBridgeDelegate {

    func sourceURL(for bridge: RCTBridge!) -> URL! {
        return URL(string: "http://localhost:8081/index.bundle?platform=ios")
    }
}

react native bridge delegate localhost
react native bridge delegate localhost

react native error on device failed bundle
react native error on device failed bundle

If we try to build this app on an iPhone, and we open one of the React Native screen we will receive the following error. This is a consequence of the fact that we are trying to access localhost from the iPhone, and our React Native node server is running on the MacBook Pro where we are building the app.

React Native XCode Builder

How can we build on a real device? First of all we need to add a new build phase to our project that let us run the React Native Xcode Bundler before the real build. The React Native Xcode Bundler is a shell script with name react-native-xcode.sh that you can find inside your react native npm package under <you app root folder .>/node_modules/react-native/scripts/. This script must take as input our React Native index.js.

react native setup bundler
react native setup bundler

Now we can change our ReactNativeBridgeDelegate implementation. Instead of returning an hard coded url, we use the RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index", fallbackResource: nil) method. We need to pass "index" as bundle root parameter (the name of the main js file).

class ReactNativeBridge {
    let bridge: RCTBridge

    init() {
        bridge = RCTBridge(delegate: ReactNativeBridgeDelegate(), launchOptions: nil)
    }
}

class ReactNativeBridgeDelegate: NSObject, RCTBridgeDelegate {

    func sourceURL(for bridge: RCTBridge!) -> URL! {
-        return URL(string: "http://localhost:8081/index.bundle?platform=ios")
+        return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index.bundle", fallbackResource: nil)
    }
}

Now we can try to build an run again the app on a real device. As you can see now everything works as expected.

react native app working on device
react native app working on device

What’s happening?

What’s happening under the hood? Which kind of “magic” are we using here emoji-smirk? If we start to debug from the call to RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index", fallbackResource: nil) and we go inside the React Native source code at some point we will see a call to a method named guessPackagerHost. In this method there’s a piece of code that tries to open and read the content of a file named ip.txt (this file is supposed to be in the main bundle of the app). The string returned by this method is used as hostname in the url used by React Native to call the packager we are running on our Mac.
Who did create this ip.txt file? Previously we added the execution of the React Native Bundler script as build phase. If we look at the source code of this script you will find the following piece of code:

react native ip txt generation
react native ip txt generation

What?!?!?!?!?!? emoji-satisfied This piece of code basically creates a file named ip.txt that contains the IP address of your computer, extracted using an ifconfig command, concatenated with the domain xip.io. So the file will contain a string like the following one: <your computer IP address>.xip.io. This is the string returned by the guessPackagerHost method. In the screenshot below you can find the source code of this method and the string that it returns.

react native my local ip
react native my local ip

What is the xip.io string added after the IP address? xip.io is a public free DNS server created at basecamp. Below you can find a quote from the homepage of the service:

What is xip.io? xip.io is a magic domain name that provides wildcard DNS for any IP address. Say your LAN IP address is 10.0.0.1. Using xip.io,

      10.0.0.1.xip.io   resolves to   10.0.0.1
  www.10.0.0.1.xip.io   resolves to   10.0.0.1

mysite.10.0.0.1.xip.io resolves to 10.0.0.1 foo.bar.10.0.0.1.xip.io resolves to 10.0.0.1

…and so on. You can use these domains to access virtual hosts on your development web server from devices on your local network, like iPads, iPhones, and other computers. No configuration required!

How does it work? xip.io runs a custom DNS server on the public Internet. When your computer looks up a xip.io domain, the xip.io DNS server extracts the IP address from the domain and sends it back in the response.

react native xip.io
react native xip.io

This basically means that xip.io is a domain name we can use to access our local packager environment on our Mac from our iPhone and iPad, as long as the devices are connected to the same network.
That’s all, and as you can see everything works “like magic” emoji-relaxed.


Read next

SwiftUI and the Text concatenations super powers

SwiftUI and the Text concatenations super powers

fabrizio_duroni
fabrizio duroni
marco_de_lucchi
marco de lucchi

Do you need a way to compose beautiful text with images and custom font like you are used with Attributed String. The Text component has everything we need to create some sort of 'attributed text' directly in SwiftUI. Let's go!!! [...]

A Monorepo Experiment: reuniting a JVM-based codebase

A Monorepo Experiment: reuniting a JVM-based codebase

luigi_noto
luigi noto

Continuing the Monorepo exploration series, we’ll see in action a real-life example of a monorepo for JVM-based languages, implemented with Maven, that runs in continuous integration. The experiment of reuniting a codebase of ~700K lines of code from many projects and shared libraries, into a single repository. [...]