Effortless React Native Deployment: Leveraging Github Actions for Automation
Overview
Are you tired of the time-consuming manual process of building and deploying React Native applications? In this article, I'll introduce an automation workflow using Github Actions, a game-changer for future mobile app development projects. I'm documenting this process here, so I don't forget it in the future.
Setting Up the Android Workflow
To get started with automating the Android deployment process, create a directory from your project root .github/workflows/ and create a new workflow called deploy-android.yml with the following content:
name: Deploy android app
on:
push: # explanation1
branches:
- main
jobs:
deploy-android:
runs-on: ubuntu-latest
container: reactnativecommunity/react-native-android
steps:
- name: Checkout # explanation2
uses: actions/checkout@v2
- name: Set up JDK environment # explanation3
uses: actions/setup-java@v1.4.3
with:
java-version: 1.8
- name: Set up Android SDK # explanation4
uses: android-actions/setup-android@v2
- name: Set up Android Build-tools # explanation5
uses: maxim-lobanov/setup-android-tools@v1
with:
cache: true
packages: |
platforms;android-29
build-tools;29.0.3
- name: Extract branch name # explanation6
shell: bash
run: echo "::set-output name=branch::${GITHUB_REF#refs/heads/}"
- name: Get yarn cache directory path # explanation7
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Restore node_modules from cache # explanation8
uses: actions/cache@v2
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install dependencies # explanation9
uses: actions/cache@v2
with:
path: ~/.gradle/caches
key: ${{ runner.os }}-gradle-wrapper-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-caches-
- name: Bundle asset # explanation10
run: |
npx react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest android/app/src/main/res/
- name: Remove duplicate resource # explanation11
run: |
git config --global --add safe.directory /__w/mcportal-app/mcportal-app && git ls-files --others --exclude-standard | grep drawable | xargs rm
- name: Build Artifact # explanation12
run: |
cd android && ./gradlew assembleDevelopment --no-daemon
- name: Sign APK # explanation13
id: sign_app
uses: r0adkll/sign-android-release@v1
with:
releaseDirectory: android/app/build/outputs/apk/Development/release
signingKeyBase64: ${{ secrets.ANDROID_SIGINING_KEY }}
alias: ${{ secrets.ANDROID_ALIAS }}
keyStorePassword: ${{ secrets.ANDROID_KEY_STORE_PASSWORD }}
keyPassword: ${{ secrets.ANDROID_KEY_PASSWORD }}
- name: Upload artifact to deploy gate # explanation14
working-directory: ./android
run: |
curl \
-H "Authorization: token ${{secrets.DEPLOY_GATE_API_KEY}}" \
-F "file=@app/build/outputs/apk/Development/release/app-Development-release.apk" \
-F "message=$(git rev-parse --short HEAD)" \
-F "distribution_name=${{ steps.extract_branch.outputs.branch }}" \
-v "https://deploygate.com/api/users/${{secrets.DEPLOY_GATE_USER_NAME}}/apps"
I've assigned an explanation number to each step, which I'll clarify here.
Explanation 1: Configuring Triggers in Github Actions
GitHub Actions offers a wide range of triggers to schedule workflows. This command instructs the Continuous Integration (CI) system on when to execute the workflow. For more details, you can explore further in the provided resource.
Explanation 2: Setting Up the Workflow
Each job in GitHub Actions represents a clean daemon process. To ensure that the workflow runs on the correct remote-tracking branch, we must follow it up with the 'git checkout' process.
Explanation 3: JDK Environment Setup
For building the Android application, it's essential to establish the Java Development Kit (JDK) environment.
Explanation 4: Android SDK Configuration
However, JDK alone isn't sufficient. We also need to configure the Android SDK to enable the installation of build tools.
Explanation 5: Build Tools Installation
To build the Android app successfully, we must install build tools. I've chosen version 29.0.3, as it aligns with the APK signing process. You can opt for different versions, but remember to adjust it when signing the APK in the subsequent step.
Explanation 6: Extracting the Branch Name
This step involves extracting the branch name, which is necessary for referencing when uploading to DeployGate.
Explanation 7: Fetching Yarn Cache Directory
Fetching the Yarn cache directory is crucial as it can significantly reduce the build time in most cases.
Explanation 8: Restoring node_modules from Cache
Similarly to the previous step, restoring 'node_modules' from the cache can save substantial build time in many scenarios.
Explanation 9: Installing Dependencies
This step installs the dependencies listed in 'package.json' while utilizing '--frozen-lockfile' to ensure consistent package version installation.
Explanation 10: Asset Bundling
Asset bundling is a crucial step in the process, just as it would be when performed manually.
Explanation 11: Removing Extraneous Resources
During asset bundling, certain additional resources can be copied, potentially leading to build errors. To prevent this, we delete these resources before initiating the build process, leveraging Git commands on the server.
Explanation 12: Building the APK
To build the APK file, we employ the 'assembleDevelopment' method in this case, as I have a 'Development' flavor specified in my 'build.gradle.' If you don't have additional flavors, the typical scenario would be 'assembleRelease.'
Recommended by LinkedIn
Explanation 13: APK Signing
This step involves signing the generated APK file in preparation for deployment.
Explanation 14: Uploading to DeployGate To finalize the process, we upload the APK file to DeployGate using the DeployGate API.
Within the aforementioned workflow configuration, six secrets must be added to the repository. Let's delve into each one individually for a comprehensive understanding.
ANDROID_SIGNING_KEY: This secret corresponds to the .keystore file used in manual processes, generated with the following command:
$ keytool -genkeypair -v -storetype PKCS12 -keystore my-upload-key.keystore -alias my-key-alias -keyalg RSA -keysize 2048 -validity 10000
Creating the Key File If you don't have this key file, it's necessary to create one. Every trigger in GitHub Actions functions as a cron process, and once it completes, it's terminated, whether it ends in success or failure. In this context, we cannot directly place the .keystore file on the server because GitHub Actions secrets only support string values. So, how do we handle this situation? The solution is to convert the .keystore file into a base64-encoded string. You can achieve this by using the command below:
$ openssl base64 < my-upload-key.keystore | tr -d '\n' | tee my-upload-key.keystore.base64.txt
Saving Secrets in GitHub Actions
Secrets to Configure:
Setting Up the iOS Workflow
Just like the Android setup, create a file named 'deploy-ios.yml' within the '.github/workflows' directory and insert the following contents:
name: Deploy iOS app
on:
push:
branches:
- main
jobs:
deploy-ios:
runs-on: macOS-latest
defaults:
run:
working-directory: ios
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Extract branch name
shell: bash
run: echo "::set-output name=branch::${GITHUB_REF#refs/heads/}"
id: extract_branch
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Restore node_modules from cache
uses: actions/cache@v2
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install dependencies
run: yarn install --frozen-lockfile --network-timeout 300000
- name: Setup Ruby # explanation1
uses: ruby/setup-ruby@v1
with:
ruby-version: 2.7.4
bundler-cache: true
- name: Restore Pods Cache # explanation2
uses: actions/cache@v2
with:
path: |
ios/Pods
~/Library/Caches/CocoaPods
~/.cocoapods
key: ${{ runner.os }}-pods-${{ hashFiles('ios/Podfile.lock') }}
restore-keys: |
${{ runner.os }}-pods-
- name: Install Pods # explanation3
run: pod install --repo-update && cd ..
- name: Build iOS App # explanation4
uses: yukiarrr/ios-build-action@v1.4.0
with:
project-path: ios/MyApp.xcodeproj
p12-base64: ${{ secrets.IOS_P12_BASE64 }}
mobileprovision-base64: ${{ secrets.IOS_MOBILE_PROVISION_BASE64 }}
code-signing-identity: 'Apple Distribution'
team-id: ${{ secrets.IOS_TEAM_ID }}
certificate-password: ${{ secrets.IOS_CERTIFICATE_PASSWORD }}
workspace-path: ios/MyApp.xcworkspace
scheme: MyApp-dev
export-method: "ad-hoc"
output-path: "ios/MyApp.ipa"
update-targets: |
MyApp-dev
- name: Upload artifact to deploy gate # explanation5
run: |
curl \
-H "Authorization: token ${{secrets.DEPLOY_GATE_API_KEY}}" \
-F "file=@MyApp.ipa" \
-F "message=$(git rev-parse --short HEAD)" \
-F "distribution_name=${{ steps.extract_branch.outputs.branch }}" \
-v "https://deploygate.com/api/users/${{secrets.DEPLOY_GATE_USER_NAME}}/apps"
Explanation 1: Ruby Prerequisite Ruby is an essential requirement for executing pod commands.
Explanation 2: Caching Pods Restoring the pods cache can significantly reduce build time.
Explanation 3: Pod Installation Before building the app, it's crucial to run 'pod install' to manage dependencies.
Explanation 4: Building the App For this step, I've utilized my own configuration settings. If you wish to define your own settings, please refer to the official documentation.
Explanation 5: Placing the IPA File In 'Explanation 4,' I've positioned the IPA file in the 'ios' directory because, at this point, I'm already within the 'ios' directory.
NOTE: The method used to generate a base64 string is the same as what was explained for the ANDROID_SIGNING_KEY above.
Conclusion
In this article, we've embarked on a journey to streamline the arduous process of building and deploying React Native applications. The manual labor of these tasks can be not only time-consuming but also prone to human error. With the power of automation using GitHub Actions, we've paved the way for a more efficient, error-free future in mobile app development.
Throughout our journey, we've covered a range of crucial steps, from setting up Android and iOS workflows to managing secrets and optimizing build times. We've demystified the process of transforming key files into base64 strings and configuring GitHub Actions for success.
As we wrap up this guide, it's essential to emphasize the importance of automation in modern app development. With this newfound knowledge, you're now equipped to leverage the full potential of GitHub Actions and harness its capabilities for your projects. By automating build and deployment processes, you'll save time, reduce errors, and ensure a smoother development experience for yourself and your team.
The road to automation might seem daunting at first, but the benefits it offers are undeniable. So, the next time you embark on a React Native project, remember that GitHub Actions is your trusted companion in achieving a seamless and efficient development process. Now, you can take this knowledge forward and apply it to your future mobile app endeavors.
Happy coding and automated deploying!