343max.de

Expo Dev Client Apps in TestFlight

tldr: testflight-dev-deploy allows you to distribute Expo Dev Client App for your developers through TestFlight, making it much easier to install and update their dev apps on iOS, iPadOS and even macOS.

What?

If you build apps using React Native you are probably using Expo as it makes it much easier to get up and running. Even the official React Native documentation recommends to use Expo.

Once your project gets more mature you probably want to switch to the Expo Dev Client as this gives you more control over your project, build process and developer experience.

One big downside of using the Expo Dev Client is how much harder it makes it to deploy Dev Client Apps to the developers devices for testing and development, particular on iOS. Getting it to run on a device is challenging even for experienced developers and nearly impossible for non-technical team members like designers or managers.

My Expo plugin testflight-dev-deploy makes this process so much easier by allowing you to deploy the dev client through TestFlight, the same way you would deploy any other pre-release build.

Advantages

  • team members can install the dev client app on all their devices through TestFlight
  • no device identifier needs to be registered, no rebuild of the app is needed
  • TestFlight also keeps the dev client app up to date on all test devices
  • TestFlight Dev Client apps can also be installed on recent macOS devices, making it possible to develop on a MacBook without having to install Xcode or the Simulator at all.

Installation

Install the plugin in your project using the expo command line tool:

npx expo install testflight-dev-deploy

now you need to add the plugin to your app.json config file in the plugins section like this:

{
	"plugins": [
		["testflight-dev-deploy", { "enabled": true }]
	]
}

Lastly you need to add or update the development build profile in your eas.json. Make sure to include the developmentClient in the profile and set distribution to store instead of internal:

{
	"build": {
		"development": {
			"developmentClient": true,
			"distribution": "store"
		}
	}
}

Now you can trigger a new build through eas and submit it to the AppStore like you would do with any AppStore or TestFlight build:

npx eas build --profile development --platform ios --auto-submit

Now in AppStore connect you can invite members of your team to install this build on the internal TestFlight testing track.

How does it work?

React Native apps can be built in two different ways: the production build is the one that will be submitted to the AppStore and be used by end users. The development build will communicate to a metro dev server and is usually installed in the iOS simulator. The Expo dev client relies on these development builds. Besides the core infrastructure development builds include a bunch of tools for developers like view inspectors. Some of the code that is only included in these development builds uses so called “private API”: functions that do exists in iOS but are not officially documented in Apples documentation. Apple doesn’t allow the use of private API and will automatically reject every app you try to submit to the AppStore or to TestFlight.

So in order to deploy an Expo Dev Client App through TestFlight I needed to make sure the build doesn’t use any private API. Luckily for me that turned out to be relatively easy: the only place where React Native uses private API is in the RCTKeyCommand.m. This helper class is supposed to handle some shortcuts for developers in the simulator like Command-R to reload the project or Command-I to toggle the element inspector. From my experience these shortcuts don’t work reliably anyways so the easiest solution was to simply disable them.

This can be done by patching a simple macro in RCTKeyCommand.m. This is easier said then done: we don’t want to fork React Native for this change so we need to monkey patch this file. That can be done through the Podfile a file to configure Cocoapods dependencies for iOS project. A Podfile is just a ruby script that runs during build so we can abuse it to patch this specific file. Modern Expo projects don’t include the Podfile in the project repository anymore, it’s generated by Expo during build. But expo plugins have hooks, so I built a plugin that patches the Podfile to include some code to turn a 1 into a 0 in RCTKeyCommand.m.