I love automating the publishing process for my Expo apps using GitHub. I already use GitHub for version control, it's the perfect tool for managing builds, especially when I work across different operating systems like Windows and Ubuntu.
Building Android apps isn’t supported on Windows. Luckily, I can just push to GitHub and let it do the work.
Before publishing my app to production, I always test it. That's what test tracks are for. My app is first deployed to the test track for internal review before going live.
My workflow:
- Create a feature branch and do work on it
- MR to main after lint and tests pass
- When main is merged, a release workflow is triggered
- Release workflow creates a new MR with a bumped version. I need to approve it.
- If approved, a new merge to main is made and release to test track is triggered
Here's what I do with a diagram:
What do you need
- You need EXPO_TOKEN: expo local build won't work without EXPO_TOKEN.
- You need a token for Play Developer API.
- Automated versioning with Github and Release Please - this one is optional, but it will be easier if you know how automated release process works.
Keys are set in Github repository Settings -> Secrets and variables -> New Repository Secret.
Setup eas.json build profiles
You need eas.json
configuration because eas will be called locally, and there is just one profile that you need to build for production, but actually you need 3 environments. I like to call them development, preview and production. Example file:
{
"build": {
"development": {
"developmentClient": true,
"distribution": "internal",
"env": {
}
},
"preview": {
"distribution": "internal",
"env": {
}
},
"production": {
"env": {
}
}
},
"cli": {
"version": ">= 9.0.0",
"appVersionSource": "local"
}
}
- development profile for android creates an apk with bundled expo dev client that you can install on your smartphone and emulator, and then you can run expo on your machine and preview the app while you develop and change the code.
- production profile for android is your app built as an aab which you can manually or automatically upload to test tracks.
- preview profile is almost the same as the production profile, it's just apk instead of aab extension. It's purely your app built as apk.
appVersionSource
local means that versioning of the app is handled locally.
Given that I have three build profiles, my build scripts are:
"build:android-dev": "eas build -p android --local --profile development --output app-dev.apk",
"build:android-preview": "eas build -p android --local --profile preview --output app-preview.apk",
"build:android": "NODE_ENV=production expo prebuild --clean && eas build -p android --local --profile production --output app-release.aab",
Environment variables
env is everything that you have in .env file that is read while you're running expo. You need to explicitly specify environment variables in the eas.json. Don't put anything there that isn't supposed to be public.
Maybe obvious or maybe not, but you can have different environment variables per profile.
Internal Test Track
I forgot how this goes, but I remember it being easy. So, if you’ve created a new app and want to distribute it via the Internal Test Track, you need to configure the internal test track in the Google Play Console. Go to the Google Play Console and log in. Select or create your app, complete the initial setup if necessary. Go to internal test track and fill in the internal tester emails. Otherwise you won't be able to install it on your device. Testers just need to open the invitation link and enroll in the testing program.
GitHub Workflow
Check that you've set the necessary environment variables in repository settings.
This workflow works in 2 phases. First it detects that the version needs to be bumped. If you want to know more about automating version bumping, I recommend that you read my tips on bumping expo. I need to approve the version bump merge request. Then the second phase is triggered, and release to internal test track is called:
name: Release Please
on:
push:
branches:
- main
permissions:
contents: write
pull-requests: write
jobs:
release-please:
runs-on: ubuntu-latest
outputs:
app--tag_name: ${{ steps.release.outputs.app--tag_name}}
app--release_created: ${{ steps.release.outputs.app--release_created}}
steps:
- uses: actions/checkout@v4
- uses: googleapis/release-please-action@v4
id: release
with:
token: ${{ secrets.GITHUB_TOKEN }}
config-file: release-please-config.json
manifest-file: .release-please-manifest.json
release2play-internal-test-track:
needs: release-please
runs-on: ubuntu-latest
if: ${{needs.release-please.outputs.app--release_created}}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}
- run: npm i
- name: 🏗 Setup EAS
uses: expo/expo-github-action@v8
with:
expo-version: latest
eas-version: latest
token: ${{ secrets.EXPO_TOKEN }}
packager: npm
- name: Build aab
run: npm run build:android
- name: Upload to Play Store Internal Testing
uses: r0adkll/upload-google-play@v1
with:
serviceAccountJsonPlainText: ${{ secrets.PLAY_CONSOLE_SERVICE_ACCOUNT_JSON }}
packageName: package.name
releaseFiles: app-release.aab
track: internal
status: draft
inAppUpdatePriority: 2
This workflow will build the production aab and upload it to internal test track. Test track will be in a draft status. It doesn't need to be, but I like to manually approve my test track publications.
You can also publish to GitHub releases
You might need a development apk and your machine might not be suited for building the apk. Maybe you're working from Windows or whatever the reason, you can use GitHub to build it. You can store your apk in GitHub releases. I created a manual dispatch workflow that builds and uploads the apk. Then I can download and install it on my usb connected device or emulator with:
adb install app-dev.apk
In case of an error while installing, uninstall the package first:
adb uninstall your.package
Workflow that publishes apk to github releases looks like:
name: Release DEV
on:
workflow_dispatch:
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup-node-environment
with:
node-version: '20'
cache-path: 'app/node_modules'
cache-key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}
project: 'app'
- uses: ./.github/actions/setup-expo
with:
expo-token: ${{ secrets.EXPO_TOKEN }}
- name: Build development
run: npm run build:android-dev
- name: Create GitHub Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: v${{ github.run_number }}
release_name: Snapshot Release v${{ github.run_number }} - ${{ github.ref_name }}
body: |
WIP development pre-release
draft: false
prerelease: true
- name: Upload apk to GitHub Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh release upload ${{ steps.create_release.outputs.upload_url }} ./app-dev.apk
Reusable Expo workflow
In this workflow you might have noticed there are some custom actions like ./.github/actions/setup-expo
. This kind of action is called a composite action and I use this pattern often to minimize repetition and make the already big workflow more readable:
name: "Setup Expo"
description: "Sets up java 17 and expo env"
inputs:
expo-token:
description: 'Expo Token'
required: true
default: ''
runs:
using: 'composite'
steps:
- name: Set up Java 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'adopt'
- name: 🏗 Setup EAS
uses: expo/expo-github-action@v8
with:
expo-version: latest
eas-version: latest
token: ${{ inputs.expo-token }}
packager: npm
Action is composed of 2 steps. If I didn't set up java 17 jvm, I'd get an error like:
Could not resolve com.google.firebase:firebase-crashlytics-gradle:3.0.0.
#[RUN_GRADLEW] Required by:
#[RUN_GRADLEW] project :
#[RUN_GRADLEW] > Dependency requires at least JVM runtime version 17. This build uses a Java 11 JVM.
If you write your own github actions, I'd recommend that you make use of this pattern. It makes workflow configurations easier to maintain.
When using the workflow to publish to github releases, workflow needs permissions to read and write. You can either set it up in repository settings or set permissions in a workflow file.
GitHub: https://github.com/amarjanica/react-native-sqlite-expo-demo
Youtube Video: https://youtu.be/StMaBOjYOZU