Expo is an environment that contains services and tools to develop mobile applications written in React Native. It brings us a super useful ecosystem, which covers creating, building, publishing and updating RN apps right from CLI. This solution is blazing fast and after a few minutes, we can have complete builds, ready to run on our devices.
I think the most recognizable tool in Expo’s ecosystem is Expo mobile client, which allows us to test our application during development (with live reloading) only by scanning QR code from our terminal. This brilliant solution speeds up our work and makes it way easier!
But Expo doesn’t support only development, it helps us also in building and publishing process, by preparing packages for Android and iOS. We have to only upload our sources to Expo cloud and after a few minutes, our application is ready to download. And this is the moment when you want to consider standalone Expo build.
What do I mean by standalone Expo build? It’s a self-hosted application, which uses Expo tools and libraries without Expo’s cloud services for building, publishing, and updating. You may ask: why do I want to resign from this comfort and fast solution and build the same on my servers? There could be several answers to this:
Expo is great and useful. We don’t want to resign from it just because of the above. Let’s create our own services to build and publish a mobile application.
Let’s check also, how to build standalone Expo mobile apps with Github Actions!
Each mobile application has to be signed by specific credentials and keys. Since we decided to don’t use Expo cloud to publish our app (which means also building and updating), we have to take care of generating keys as well. Fortunately, it’s not a big problem if you have access to a terminal (for Android builds) and macOS (for iOS builds).
Apple prepared for us a long path to generate required certificates. We have to start on the Certificates List, which is available from our developer’s account: https://developer.apple.com/account/resources/certificates/list.
If you plan to use push notifications in your application, it’s a good moment to generate also APN (Apple Push Notification) key. You can find it in Keys section. To generate .p8 file, you have to just provide some key name and select the APN option from the list.
The last step is to generate .p12 file, which is needed by Expo building tool. We can export such a file from Keychain Access Manager on our macOS, but before that, we need to install the certificate which we created at the very beginning (step 2).
After all these steps, we should have a complete list of required files, which means:
For Android, on the opposite, we need one file, which we can generate by one simple CLI command. In your terminal just run keytool script and answer several questions about the certificate’s owner. This is the “magic” command:
$ keytool -genkeypair -v -keystore {filename}.keystore -alias {keystore-alias} -keyalg RSA -keysize 2048 -validity 10000
After that, we have our password-protected keystore file.
When we have all keys and certificates, it’s time to build our packages. But before we start, it’s important to know, how Expo application works (and why they allow us to update them over-the-air).
When we open our application, Expo container asks for application manifest (which contains all metadata and paths to application sources) and then it downloads the latest sources from CDN servers (in most cases it’s cloudfront) according to the platform. This is also how Over-The-Air updates work: when sources are updated and we configure our app to ask for new sources on each launch, then we can update our app just by updating sources on CDN, without pushing a new build to store.
As you may guess, if we decided to resign from Expo’s cloud support, we have to also host our sources somewhere. Actually, for Expo it’s no matter where our files will land. There is only one requirement: connection has to be secured by SSL.
Let’s assume that we have some hosting with SSL and we want to put there our sources. To do that, we need to go through some steps (they can be run on any platform - macOS, Windows or Linux)
After a few minutes, all your sources and images will be stored in ./dist folder and all these content you have to upload directly to https://your-domain-for-assets.com.
This set of assets contains manifests, images and minified bundles of our source code. It’s important to export and publish it before we start building final packages.
Expo team prepared a great tool called Turtle-CLI (https://github.com/expo/turtle#readme), which handles the building process on their side, but they turned out cool guys and they published sources on GitHub to use it standalone. Thanks to that, we can include building mobile applications on our servers in the continuous delivery process. All we need is bash, node (8 or higher) and JDK8 - for Android, or macOS and Xcode (<= 9.4.1) - for iOS build.
Of course, to build anything, you have to install globally Turtle-CLI:
$ npm install -g turtle-cli
Since Android build doesn’t need many things to run, you can just start building it on your computer or use one of the docker images, prepared for such a task. For example, we have our own image, with all prerequisites and downloaded Turtle-CLI, prepared as a Jenkins slave. You can find it on DockerHub: https://hub.docker.com/r/evojam/jenkins-slaves/tags (tag: expo-android).
To run build, you have to define the environment variable in your system:
EXPO_ANDROID_KEYSTORE_PASSWORD
- a password for the keystore which we generated in the first partEXPO_ANDROID_KEY_PASSWORD
- a password for the keyAnd that’s it, time to run our build by:
$ turtle build:android -o {filename} --keystore-path {path-to-keystore}.keystore --keystore-alias {keystore-alias} --public-url {public-url}
Let’s explain it a bit:
And after a few minutes, we should have our application ready to publish.
The building process for iOS is quite similar. Of course, all these things we have to run on macOS. First of all, we have to set an env variable with a password to .p12
EXPO_IOS_DIST_P12_PASSWORD
- a password to generated .p12 fileAfter that we can run turtle’s command:
$ turtle build:ios -o {filename} --team-id {apple-team-id} --dist-p12-path {path-to-p12} --provisioning-profile-path {path-to-pp} --public-url {public-url}
Small explanation:
This build will also take a few minutes and after that, you should be able to find IPA package in your workspace, ready to publish.
Mobile application without push notifications nowadays sounds a bit oddly. Our phone vibrates almost all the time. If you want to add such a feature to your standalone Expo app, you have to accept some compromise and store some credentials on Expo servers.
Expo allows us to use FCM directly as a notification broadcaster with one restriction: this solution doesn’t work on iOS. So, probably such an option won’t satisfy you. Me neither. Let’s configure Expo-based solution then.
Expo has its own broadcast service, which propagates our notifications through FCM straight to our applications, so we have to start from proper FCM configuration.
On Firebase Console (https://console.firebase.google.com), we have to create a new project, with two applications - Android and iOS. Android app configuration is quite simple: everything we need to do is to fill out a simple form by app name/bundle ID and to download google-service.json file at the end of this process.
iOS configuration is a bit more complicated because we have to upload our .p8 file, generated in the Apple Developer panel and provide .p8 ID and Team ID.
After configurations, we should have one extra file (google-services.json), which we save right into our project’s root directory. We have to also add a path to this file in our app.json file:
"expo": {
"android": {
...
"googleServicesFile": "./google-services.json"
}
}
From now, we have to remember to add google-services.json file to our project’s root directory for each export and build step. If we forget about it, none of these steps will pass.
Let’s go back to our terminal and push some information to Expo servers. First, we have to send FCM server key, which we can find in Firebase Console on Cloud Messaging tab. To do this, we use Expo CLI:
$ expo login
$ expo push:android:upload --api-key ${your_server_key}
And that’s everything. For Android, of course. Keep in mind that you have to be logged in before you start registering any keys.
iOS case is a bit more complicated. Since Turtle-CLI doesn’t support uploading .p8 files yet, we have to use a small, dirty workaround to cheat expo and send this authorization key to its servers somehow. We need one environment variable, which we already defined. Let’s change it to some random phrase (to avoid sending real passwords to expo servers), it could be abc or anything else.
$ export EXPO_IOS_DIST_P12_PASSWORD=abc
Next, create an empty file in your project dir and call it fake.crt and then make some magic in your terminal by running:
$ expo login
$ expo build:ios --push-id ${APN_ID} --push-p8-path ${path_to_p8_file} --apple-id ${your_apple_id} --dist-p12-path ./fake.cert --provisioning-profile-path ./fake.cert --no-publish
Some explanations:
Keep in mind, that you need properly filled app.json, with proper bundle IDs and app names. This command will start building iOS application on expo servers, but before that, you will be asked to answer two questions:
After the last confirmation, we have to wait for a second, until we get a URL to build preview, and then kill the process by CTRL+C. Just to be sure, you can check this URL and cancel the build, but probably it will be already finished because of our fake certs.
It sounds super-strange, I know, but for now, this is the only way to upload p8 file to Expo servers.
To make sure that our credentials are in the right place, we can check it by Expo CLI command:
$ expo fetch:ios:certs
The response should include our fake P12 password and proper Push Key ID.
After all these steps, we can proceed to handle notifications in our codebase according to the official documentation: https://docs.expo.io/versions/v35.0.0/guides/push-notifications/
As you can see, Expo is not only a useful library, which supports our development but also they provide a bunch of tools to build our applications on our own. Anyway, it sounds a bit overcomplicated and probably you won’t need such workarounds to build your app, but it’s good to know that there is such an option. You can migrate your codebase and processes to your servers any time. And it’s no one-way ticket, you can always decide to use Expo resources to publish your application again, but keep in mind, you have to upload all credentials, generated for a standalone app, to Expo servers, to keep the continuity of your application.
The whole process could be run on Github Actions. Check here: how to configure your own, github-based CI/CD for mobile apps.