React Native Continuous Delivery with Github Actions and Fastlane

Published on 5 years agoReact Native Continuous Delivery with Github Actions and Fastlane

Github Actions is the workflow automation tool with CI/CD that allows you to do some tasks, such as running test suite, deploying code and etc based on the Github Events and Types. When an event is triggered, your defined workflow will be run and help you to do some awesome jobs.

Today, I'm going to show you how to create the workflow of building your React Native apps and publishing them to App Store Connect and Play Store using Github Actions and Fastlane. By end of this post, we will be able to build and publish iOS and Android app to respective store when a new Github Release is published. Sounds exciting? Let's get started!

Creating Workflow

First, we will have to create a workflow in .github/workflows directory. Similar to other CI/CD services, you may configure the workflow using YAML syntax. Multiple workflow files can be created in the directory and each workflow must have at least a Job.

Now, let's create a publish.yml workflow and put a name for the workflow.

name: Publish iOS and Android App to App Store and Play Store

Setting Trigger Event

We want to trigger the workflow when a Github Release is published. Thus, we will be using the release event in Github Actions to trigger our workflow. Besides release event, Github Actions also contains many events such pull_request, push, issues that you can hook on and run the workflow. Many events such as release, pull_request may have more than one type of activities. For example, the release event will be triggered when a release is published, created, unpublished, edited and etc.

In this post, we want to trigger the workflow when the event is release and the activity type is published.

name: Publish React Native App to App Store and Play Store

+ on:
+  release:
+    type: [published]

If you want to trigger event with more than one activity type, you may add more activity type into the array. Other than web hook events, workflows can be triggered by scheduled job and external events too.

To understand more about triggering events, please read Events that trigger workflow

Creating Jobs

As mentioned above, each workflow must have at least a Job. Since we are building iOS and Android app, let's add two jobs: release-ios and release-android in the workflow.

name: Publish React Native App to App Store and Play Store

on:
  release:
    type: [published]

+jobs:
+  release-ios:
+    name: Build and release iOS app
+    runs-on: macOS-latest
+
+  release-android:
+    name: Build and release Android app
+    runs-on: ubuntu-latest

By default, every job will be started simultaneously. If you want to start a job after another job is completed, you can use needs to specify the dependencies of a job.

jobs:
  job1:
  job2:
    needs: job1
  job3:
    needs: [job1, job2]

In this example, job2 will starts after job1 is completed and job3 will starts after job1 and job2 is completed.

For every job, we will also need to specify which OS that the job should runs on, Github Actions provides three essential OS which is windows, ubuntu and macOS. In this post, we will use macOS-latest for iOS build and ubuntu-latest for Android build

To understand more about the virtual environment, please read Virtual environment for Github Actions

Defining Steps

Now, we can start to add the steps of building and releasing the iOS and Android app. Github Actions provides several standard actions for us to perform certain tasks such as checkout the repository, installing Node, installing Ruby and etc. You can create your own actions if you want to. We will use few standard actions such as actions/checkout, actions/setup-node and actions/setup-ruby to setup the environment.

name: Publish React Native App to App Store and Play Store

on:
  release:
    type: [published]

jobs:
  release-ios:
    name: Build and release iOS app
    runs-on: macOS-latest
+    steps:
+      - uses: actions/checkout@v1
+      - uses: actions/setup-node@v1
+        with:
+          node-version: '10.x'
+      - uses: actions/setup-ruby@v1
+        with:
+          ruby-version: '2.x'
+      - name: Install Fastlane
+        run: bundle install
+      - name: Install packages
+        run: yarn install

  release-android:
    name: Build and release Android app
    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v1
+      - uses: actions/setup-node@v1
+        with:
+          node-version: '10.x'
+      - uses: actions/setup-ruby@v1
+        with:
+          ruby-version: '2.x'
+      - name: Install Fastlane
+        run: bundle install
+      - name: Install packages
+        run: yarn install

In the workflow above, we have added few steps as following:

  1. actions/checkout@v1 - Checkout the current repository.
  2. actions/setup-node@v1 - Install Node 10.x to run React Native >= 0.60
  3. actions/setup-ruby@v1 - Install Ruby 2.x for the usage of Fastlane
  4. bundle install - Install Fastlane
  5. yarn install - Install NPM packages

Now, we have all the basic setup in place and we will continue to add more steps to build iOS and Android app using Fastlane. Let's start with building and publishing Android app.

Build and Publish Android app

There are 2 things that we need to build and publish Android app:

  1. keystore - Signing the APK. Learn more about how to create your keystore.
  2. Google Credentials - Authenticate with Play Console for publishing the app. Learn more about how to create your Google Credential.

After you have created the keystore and the Google Credential, you may want to encrypt them using command gpg --symmetric --cipher-algo AES256 path/to/your-secret.json and commit to your repository.

Now, let's create a script to decrypt the keystore and the Google Credential so that we can use them in our workflow. Create decrypt.sh and add the following codes:

#!/bin/sh

# --batch to prevent interactive command --yes to assume "yes" for questions
gpg --quiet --batch --yes --decrypt --passphrase="$ENCRYPT_PASSWORD" \
--output ./path/to/release.keystore ./path/to/release.keystore.gpg

gpg --quiet --batch --yes --decrypt --passphrase="$ENCRYPT_PASSWORD" \
--output ./path/to/google-key.json ./path/to/google-key.json.gpg

The ENCRYPT_PASSWORD is the password that you used to encrypt your secret files and we will put it as a environment variable later. Now let's add the remaining steps to complete the Android workflow.

name: Publish React Native App to App Store and Play Store

on:
  release:
    type: [published]

jobs:
  release-ios:
    ...

  release-android:
    name: Build and release Android app
    runs-on: ubuntu-latest
    steps:
      ...
+      - name: Decrypt keystore and Google Credential
+        run: ./path/to/decrypt.sh
+        env:
+          ENCRYPT_PASSWORD: ${{ secrets.ENCRYPT_PASSWORD }}
+      - name: Bundle and Upload to PlayStore
+        run: bundle exec fastlane build_and_release_to_play_store versionName:${{ github.event.release.tag_name }}
+        env:
+          STORE_PASSWORD: ${{ secrets.STORE_PASSWORD }}
+          KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}

We are using the name of the tag as the versionName of the app. To add environment variables in Github Actions, we can add env in the steps that need the variables. If you want to add any secrets, you may add the secrets to your Github repository's Settings and access them using ${{ secrets.YOUR_SECRET_NAME }}.

We are almost there. Let's add the build_and_release_to_play_store action in the Fastfile.

lane :build_and_release_to_play_store do |options|
  # Bundle the app
  gradle(
    task: 'bundle',
    build_type: 'Release',
    project_dir: "android/",
    properties: {
      "versionName" => options[:versionName],
      "versionCode" => options[:versionCode],
    }
  )

  # Upload to Play Store's Internal Testing
  upload_to_play_store(
    package_name: 'com.example.app',
    track: "internal",
    json_key: "./path/to/google-key.json",
    aab: "./android/app/build/outputs/bundle/release/app.aab"
  )
end

Awesome! We have completed the Android steps and you should able to build and publish the app to Play Store.

Building and Publish iOS app

To build an iOS app, we will need to sign the IPA before upload it to App Store Connect and there is no easy way of doing it in CI/CD environment. Luckily, Fastlane provides the sync_code_signing action for us to handle the code signing easily. If you have not setup code signing before, please follow the codesigning guideline to generate your certificates and provisioning profiles.

Once you done setup the code signing, you can add the steps to the workflow

name: Publish React Native App to App Store and Play Store

on:
  release:
    type: [published]

jobs:
  release-ios:
    name: Build and release iOS app
    runs-on: macOS-latest
    steps:
      ...
      - name: Login Github User
        run: echo -e "machine github.com\n  login $PERSONAL_ACCESS_TOKEN" >> ~/.netrc
        env:
          PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
      - name: Build and Upload to TestFlight
        run: bundle exec fastlane build_and_release_to_app_store versionName:${{ github.event.release.tag_name }} versionCode:${{ github.run_number }}
        env:
          FASTLANE_PASSWORD: ${{ secrets.FASTLANE_PASSWORD }}
          MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}

  release-android:
    ...

In Github Actions, we can only access the current repository that the workflow is running. If you want to checkout other private repositories, you may create a personal access token and login using netrc echo -e "machine github.com\n login $PERSONAL_ACCESS_TOKEN" >> ~/.netrc. We have added this step to allow us to checkout the code signing repository.

In addition, we will need to add FASTLANE_PASSWORD and MATCH_PASSWORD in the environment variables so that Fastlane able to decrypt the certificates and provisioning profiles as well as authenticate with App Store Connect.

If your Apple account has activated two-factor authentication, you may want to add FASTLANE_SESSION and FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD into the environment variables, otherwise the workflow will fail. Read Fastlane two-factor or two-steps auth to learn more.

Now let's add build_and_release_to_app_store actions into Fastfile.

lane :buid_and_release_to_play_store do |options|
  ...
end

lane :build_and_release_to_app_store do |options|
  # Set the build number
  increment_build_number(
    build_number: options[:versionCode],
    xcodeproj: "./ios/Example.xcodeproj"
  )

  # Set the version name
  increment_version_number(
    version_number: options[:versionName],
    xcodeproj: "./ios/Example.xcodeproj"
  )

  # Create a custom keychain for code signing
  create_keychain(
    name: 'keychain',
    password: 'password',
    default_keychain: true,
    unlock: true,
    timeout: 3600,
    add_to_search_list: true
  )

  # Import the appstore code signing
  match(
    type: "appstore",
    keychain_name: 'keychain',
    keychain_password: 'password',
    readonly: true
  )

  # Building the iOS app
  gym(
    workspace: "./ios/Example.xcworkspace",
    include_bitcode: true,
    include_symbols: true,
    silent: true,
    clean: true,
    scheme: "Example",
    export_method: "app-store"
  )

  # Upload to testflight
  testflight(
    app_identifier: "com.example.app",
    username: "your_apple_username@mail.com",
    skip_submission: true,
    skip_waiting_for_build_processing: true
  )
end

Awesome! We have completed the iOS job! Now you should able to build and publish your iOS app to App Store Connect now.

Testing Your Workflow

To test your workflow, you can create a Release and go to the Actions tab in Github to view the log of your workflow.

Github Actions is here for you!

Github Actions is a very awesome tool for every developer to start with CI/CD journey. To understand more about Github Actions, please read their documentation. Have fun trying out Github Actions 😊 !

Copyright © 2024 Tek Min Ewe