Using CircleCI, Expo EAS, BrowserStack to build React Native Application Deployment Pipeline

I recently got a chance to work within a Frontend team which delivers iOS and Android applications to our customers. One of the pain point in the team was the difficulty in app testing and release so I spent some time on investigating the best approach for the team. The team is using React Native based Expo and at this stage highly replying on manual testing on physical devices. They are currently putting efforts on automated testing based on Detox.

Expo EAS

Expo provides a React Native build tool called Expo Application Services (EAS), it allows you to build and submit applications easily. By default when you run eas build, it will create a build on their cloud services and you will have to pay for priority pipeline execution. The Free plan at the time of writing only allows you to build one pipeline at a time. In our case we already have CircleCI subscription so it would be better if we can build app bundles there. Fortunately there is a --local option which allows building app bundles locally. You will still need to set up EAS on Expo, for detail guide, please refer to their setup guide. Here is an example command we use to build our app bundles using CircleCI MacOS and JVM executor.

eas build --profile preview --platform ios --clear-cache --local --non-interactive --output "my-app.ipa"

We use eas submit to automatically push master/production build to app store and play store after manual approval. The key here is that for BrowserStack we need to use internal distribution profile so that we can install on their devices. You don’t have to add their remote devices to Expo as BrowserStack re-signs the bundles upon upload so that they can be installed on their devices.

BrowserStack

To make current manual testing easier I decided to introduce BrowserStack App Live, it allows the team to test the app using remote real devices and it offers quite a lot of iOS and Android options. It also comes with local testing which allows us to connect to our backend services hosted behind our vpn. There are some options for getting access to the app. You can upload app .ipa and .apk bundles, or install via Apple TestFlight and Google Play. To test push notification, please refer to guide here. If you are uploading the .ipa Apple bundle, unfortunately it is not supported at the time of writing, the only way to test push notification on iOS is installation via TestFlight.

Overall Pipeline View

Here is what our overall pipeline looks like:

For branch build we have a manual trigger to push bundles to BrowserStack as we don’t want every commit to go through manual QA process. We only trigger this once the PR has been reviewed and ready for QA. The bundles are also uploaded to S3 so that we can retrieve them and install locally easily. You will need to register your test physical devices with Expo using eas device:create so that the bundles can be installed. See internal distribution setup here.

For master/main branch build we went through the similar process with the only difference that builds are automatically pushed to BrowserStack with a manual trigger to push to production following production profile build. We use standard-version to automatically increase our app version so that every build is able to be released. The BrowserStack preview here is quite useful during our review session and following that we will decide whether the candidate version is good enough for release.

CircleCI config

Here are some useful CircleCI steps for the pipeline described above:

Build Android Using OpenJDK executor
eas-build-android:
    executor: android
    parameters:
      profile: 
        description: distribution profile
        type: string
        default: preview
    environment:
      PROFILE: << parameters.profile >>
    steps:
      - attach_workspace:
          at: ~/
      - node/install:
          install-yarn: true
          node-version: '16.9.1'
      - run: echo -n $PLAY_STORE_SERVICE_ACCOUNT_KEY | base64 -d  > serviceAccountKey.json        
      - run:
          name: android build
          command: |
            mkdir android_sdk
            curl -O https://dl.google.com/android/repository/commandlinetools-linux-8512546_latest.zip
            unzip commandlinetools-linux-8512546_latest.zip
            mv cmdline-tools android_sdk
            mkdir -p android_sdk/cmdline-tools/tools
            mv -t android_sdk/cmdline-tools/tools android_sdk/cmdline-tools/lib android_sdk/cmdline-tools/bin android_sdk/cmdline-tools/source.properties android_sdk/cmdline-tools/NOTICE.txt
            export ANDROID_SDK_ROOT=$HOME/project/android_sdk
            yes | $ANDROID_SDK_ROOT/cmdline-tools/tools/bin/sdkmanager --licenses || if [[ $? -eq 141 ]]; then true; else exit $?; fi
            npm install -g expo-cli eas-cli
            npm ci
            eas build --profile << parameters.profile >> --platform android --clear-cache --local --non-interactive --output "${CIRCLE_BRANCH//\//-}-${PROFILE}-${CIRCLE_SHA1:0:7}.apk"
Build iOS using MacOS executor
  eas-build-ios:
    executor: ios
    parameters:
      profile: 
        description: eas profile
        type: string
        default: preview
    environment:
      PROFILE: << parameters.profile >>
    steps:
      - attach_workspace:
          at: ~/
      - node/install:
          install-yarn: true
          node-version: '16.9.1'
      - run:
          name: ios build
          command: |
            npm install -g expo-cli eas-cli
            npm ci
            eas build --profile << parameters.profile >> --platform ios --clear-cache --local --non-interactive --output "${CIRCLE_BRANCH//\//-}-${PROFILE}-${CIRCLE_SHA1:0:7}.ipa"

We are using CircleCI NodeJS Orbs to install NodeJS on the executors. For Android we pass in service account key from pipeline secret and need to download Command Line Tools too.

Overall the pipeline works quite well for us and we have significant improvements on development process. Now product manager/designer and manual QA could easily download the bundle and play with the app on either BrowserStack or our test physical devices.