React Native DevOps made easy. Part 1. Android

April 01, 2019

What does it take to deploy the mobile application to the production? If you have an application built using React Native technology you need to care both of iTunes and Play Market - not fun at all. And I bet you don't want to spend days going down into all these subtleties of the process, right?

In StartupCraft Inc we love to spend days in research and development of the infrastructure that would pay off in a long run. Call it a smart time investment, whatever, we just prefer configure once and reuse in future.

What else do we need? CI (Continuous Integration). So we can run tests before each build and maintain different environment variables. And the way to automate the initiation of the build process. There are a few options on the market to help with this goal and we stopped by the most sophisticated one - BitRise

While BitRise helps a lot, it's a little bit challenging to the newcomers and those unfamiliar with subtle differences between iOS and Android platforms to setup and run. The goal of this article is to share experience of how we solve DevOps task at StartupCraft Inc so you can enjoy the process and look differently at the traditionally complex process of configuring DevOps infrastructure.

Since there are a lot of steps, we're gonna start from configuring system for Android devices and in the next part we'll take care of iOS system.

Step 1. Setup an account

First things first, go ahead and create the new account at https://www.bitrise.io/  Standard procedure. Once you're done - you should see an empty dashboard:

Bitrise Empty Projects page

Next, we're going to create 2 apps: iOS and Android. BitRise offers a single app in their platform to support both platforms, however, we found it messy and since we're dealing with CI flow we prefer to have them separately.

Step 2. Build Flow

To help understand the process better, we have visualized process using the schema below:

Android deployment schema

Click on “Add your first app” and begin with the project setup wizard.

1. Select an account to which project should belong to and its privacy settings. You can select "Public" in the sake of simplicity of this tutorial as we don't mind these configs to be publicly accessible.

Select account and privacy mode

2. Link to your GitHub account and select project repository from the list.

Connect GitHub and select repository

3. Unless you have public repository, private SSH key will be generated at this point. Otherwise, skip this step.

SSH Private key generated by BitRise

Private key will be added to the GitHub "Deploy Keys" section under the project settings.

Deploy Keys in GitHub project settings

4. Time to specify branch of repository to use "master" by default.

After confirming the branch, BitRise will validate the information. This process takes time so you can brew a cup of coffee or tea in the meanwhile.

Select repository branch

Next, we'll go through "Manual" process of the project installation.

5. If the "Manual" mode is selected and the configuration looks like the one on the screenshot below, it means the Android configuration is selected by default.

Android platform setup

Click "Next" and then "Confirm" to proceed. Next step will be Webhooks setup. This feature allows us automatically rebuild the app based on a certain set of actions such as commit pushes, pull request creations etc. What are you waiting for? Hit “Register a Webhook for me!” and jump to the next step!

Create app webhooks

Step 3. Webhooks setup

After the successful completion of the wizard, BitRise will automatically start first build for the newly created app. Don’t worry, it will always fail for the first build as we still need to tune it using workflow builder

Workflow build button

By following best security practices, we don't hardcode or store any kind of sensitive information inside our code or repository. In order to connect this sensitive data, not get exposed and use appropriate keys we are going to work with environment variables, integrated within the build process.

After succcessful generation of APK file we should be able to sign it securely as well. To attain this goal we have to upload environment variables in a form of a file for each environment type: testing, staging, production etc. and "keystore" files for further signature of APK file. Thankfully, BitRise offers a file storage outside of the box. Let’s switch to “Code signing” section.

Code signing section

Environment Variables setup

Each file uploaded within this section will be associated with the file path accessible through respective environment variable. Let's test it via uploading environment file for production and staging environments. “.env” is used for production and “.env.dev” is used for staging.

First of all, you need to specify a name of the file to upload, for example, we use “ENVFILE” and “ENVFILE_DEV”. Once you have them, simply upload files by clicking on the dropzone area or drag and drop over it.

Uploaded

Now both files will be accessible throughout building process.

"Keystore" file

To allow BitRise sign generated APK file of the Android application we need to upload "Keystore" file. If you don’t have any, you need to generate it first.

Right after uploading you will be asked to specify a password, alias and a private key password (which you’ve specified during “keystore” file creation process, see screenshot below). Values of these fields will also be accessible throughout building process of the app.

Setup keystore file

BitRise configuration setup

Let’s switch to “bitrise.yml” tab and replace all content there with the config below.

bitrise.yml

  
---
format_version: '6'
default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git
project_type: android
workflows:
  _init_install:
    steps:
    - activate-ssh-key@4.0.3:
        title: Activate App SSH key
        run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}'
    - git-clone@4.0.14: {}
    - script@1.1.5:
        title: Install npm-cache
        inputs:
        - content: |-
            #!/bin/bash
            npm install -g npm-cache
    - cache-pull@2.0.1: {}
    - script@1.1.5:
        title: npm-cache install
        inputs:
        - content: npm-cache install --cacheDirectory .
    - cache-push@2.0.5:
        inputs:
        - cache_paths: "./npm"
    meta:
      bitrise.io:
        stack: linux-docker-android
  _make_apk:
    steps:
    - change-android-versioncode-and-versionname@1.1.1:
        inputs:
        - build_gradle_path: android/app/build.gradle
        - new_version_code: "$BITRISE_BUILD_NUMBER"
    - file-downloader@1.0.1:
        inputs:
        - destination: "$ENVFILE_DESTINATION"
        - source: "$ENVFILE_URL"
    - script@1.1.5:
        inputs:
        - content: |-
            #!/usr/bin/env bash

            echo "Increase inotify file watch limit to achieve building multiple apk in the same workflow"

            # fail if any commands fails
            set -e
            # debug log
            set -x

            # INFO: https://github.com/guard/listen/wiki/Increasing-the-amount-of-inotify-watchers#the-technical-details

            eval "echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p"
    - script@1.1.5:
        title: gradlew
        inputs:
        - content: export ENVFILE=$ENVFILE && cd android && ./gradlew $GRADLEW_TASKNAME
    - sign-apk@1.2.4:
        inputs:
        - apk_path: "/bitrise/src/android/app/build/outputs/apk/$GRADLE_FLAVOR_PRODUCT/$UNSIGNED_APK_FILENAME"
        - keystore_url: "$BITRISEIO_ANDROID_KEYSTORE_URL"
        - keystore_password: "$BITRISEIO_ANDROID_KEYSTORE_PASSWORD"
        - keystore_alias: "$BITRISEIO_ANDROID_KEYSTORE_ALIAS"
        - private_key_password: "$BITRISEIO_ANDROID_KEYSTORE_PRIVATE_KEY_PASSWORD"
    - script@1.1.5:
        title: cp apk
        inputs:
        - content: cp $BITRISE_SIGNED_APK_PATH $BITRISE_DEPLOY_DIR/$SIGNED_APK_FILENAME
    - deploy-to-bitrise-io@1.3.19:
        is_always_run: false
        inputs:
        - notify_email_list: ''
    before_run: 
    after_run: 
  _slack_message:
    steps:
    - slack@3.1.2:
        inputs:
        - webhook_url: "$SLACK_WEBHOOK_URL"
        - channel: "#bitrise"
        - from_username: Android $ENVIRONMENT Build Succeeded
        - from_username_on_error: Android $ENVIRONMENT Build Failed
        - message: 'Test the changes on the latest $ENVIRONMENT build. Click here
            to download the build: $BITRISE_PUBLIC_INSTALL_PAGE_URL'
        - message_on_error: 'Click here to find out why the build failed: $BITRISE_PUBLIC_INSTALL_PAGE_URL'
        - emoji: ":confetti_ball:"
        - emoji_on_error: ":shrug:"
  development:
    steps:
    - script:
        title: Set Environment
        inputs:
        - content: |-
            #!/bin/bash

            envman add --key ENVFILE --value .env.dev
            envman add --key ENVFILE_URL --value "$BITRISEIO_ENVFILE_DEV_URL"
            envman add --key ENVFILE_DESTINATION --value ".env.dev"
            envman add --key ENVIRONMENT --value "[DEV] Synchronic"
            envman add --key GRADLEW_TASKNAME --value assembleDevelopmentRelease
            envman add --key UNSIGNED_APK_FILENAME --value app-development-release-unsigned.apk
            envman add --key GRADLE_FLAVOR_PRODUCT --value development
            envman add --key SIGNED_APK_FILENAME --value development.apk
    before_run:
    - _init_install
    after_run:
    - _make_apk
    - _slack_message
  production:
    steps:
    - script:
        title: Set Environment
        inputs:
        - content: |-
            #!/bin/bash

            envman add --key ENVFILE --value .env
            envman add --key ENVFILE_URL --value "$BITRISEIO_ENVFILE_URL"
            envman add --key ENVFILE_DESTINATION --value ".env"
            envman add --key ENVIRONMENT --value "[PROD] Synchronic"
            envman add --key GRADLEW_TASKNAME --value assembleProductionRelease
            envman add --key UNSIGNED_APK_FILENAME --value app-production-release-unsigned.apk
            envman add --key GRADLE_FLAVOR_PRODUCT --value production
            envman add --key SIGNED_APK_FILENAME --value production.apk
    before_run:
    - _init_install
    after_run:
    - _make_apk
    - _slack_message
trigger_map:
- push_branch: production
  workflow: production
- push_branch: master
  workflow: development
- push_branch: bitrise
  workflow: development
  

Phew! That's a lot! Although it isn't as complex as it look like. Each workflow in Bitrise can be separated as a self-contained, specific task. To understand it better, let's review every workflow separately.

  • development and production:

    High-level workflows. They serve as setters for the global variables for respective environment defined by their name. Here we specify “env” file destination path, name of the “gradlew” task and other variables associated with the environment.

  • _init_install:

    This workflow is used within the build preparation stage. It encompases steps like downloading codebase and installing npm dependencies.

  • _make_apk:

    Make APK workflow does runs several processes:

    1. Bumps the version of the Android app and assigns build number equal to bitrise build number.
    2. Downloads “env” file from the file storage.
    3. Increases limit of allocated memory. Missing this step would quickly lead your app to getting “out of memory” error in the React Native projects with version higher than 0.57.
    4. Builds APK file with “gradlew” using environment variables specified on early stage and then signs generated APK file and copies it into the deployable directory.
    5. Finally, it deploys freshly built APK file from previous step to Bitrise public server. This allows downloading generated APK file and retrieve meta information directly from the Bitrise public installation page.

  • _slack_message:

    This is a bonus hook which serves as notifier, broadcasting updates in specified Slack channel about the build results.

  • trigger_map:

    This handy tool lets us observe changes from code repository by mapping git branch name with workflow name. For instance, at StartupCraft we run production (or development) workflow anytime new commit is pushed to production (or master) branch. The map could be specified on the tab called "Triggers".

    Triggers tab

Step 4. Slack and GitHub Incoming Webhooks

Now, while we have configured a lion share of BitRise part, we're setting our path towards the incoming webhooks for GitHub and Slack to complete the cycle.

GitHub

Select “Code” tab in Workflow editor and select “Setup manually” in the “Incoming Webhooks” section.

Incoming Webhook

In the dropdown menu, GitHub should be already selected by default. You just need to copy the webhook URL.

Incoming Webhook Setup

And time to travel to GitHub. Visit repo’s settings page and select “Webhooks” tab and click “Add webhook” button.

Incoming Webhook - GitHub

Now paste copied url into the “Payload URL” field and click on “Add webhook”. That's it! When new commits are pushed to the branches mapped with workflow you have specified in the “Triggers” section, respective workflow will get triggered.

Incoming Webhook - GitHub Finish

Slack

Not necessary yet extremely convenient webhook. Repeat the process from the Code section but select “Slack” from the apps dropdown instead of GitHub and copy suggested URL. And move to "Slack" app.

Inside the application, click on “Add apps” in the left bottom corner like on screenshot below.

Slack - Add app

Once you're done, “Browse Apps” screen will appear. You need to install application called “Incoming WebHooks”.

Slack - Browse apps

On the “Incoming WebHooks” configuration page, select channel to which BitRise build results should be broadcasted to. We use “bitrise” channel name according to our bitrise.yml configuration. If you want to use different channel name don't forget to update bitrise.yml.

Slack - Channel selection

After clicking “Add Incoming WebHooks integration” you will receive the WebHook URL.

Slack - Webhook URL

Copy and paste URL into the Secret variable field in the Bitrise “Secrets” section of the workflow builder.

Slack - Webhook Variable

Conclusion

You made it! It wasn't an easy walk and process took us a lot of steps. Does it worth it?

To summarize of what we have:

  • a fully configured system for deploying Android application.
  • integrated CI.
  • convenient webhooks which automate deployment process and keep everybody in a loop in Slack channel.

You probably won't need to return back to the configuration anytime in the future!

At StartupCraft Inc, we believe that deployment should not be a complex task as most of the complexity can and should be circumvented by software. And we hope this article proves it and clarifies every single step so you aren't stacked with unanswered "dead end" questions.

The last component for your perfect infrastructure setup would be to add a support for iOS infrastructure which you could expect in the next chapter, stay tuned!

The whole approach was developed by Sergey Laptev and successfully tested in production environment. Such solution is literally transforming an article into a recipe which we are going to re-use ourselves. No, seriously! One day we have configured the DevOps infrastructure so well that we haven't been touching it for a long time and returning back to change something was sort of memory challenge. LOL!

Let us know what you think and whether it was helpful or if it's missing something in the comments below and don't forget to share/like if you enjoyed reading the solution!

0/5
Volodymyr Katanskyi
Article by:
Volodymyr Katanskyi
Volodymyr Katanskyi
CEO at StartupCraft Inc
Volodymyr has over 12 years of experience in software development and has studied Engineering and Multicultural Education. While travelling around the world, he invented the adaptive workflow framework that helped manage a team of around 100 team members alone, stay productive and scale. This knowledge was incorporated in a form of StartupCraft Inc company.
Sergey Laptev
Idea by:
Sergey Laptev
Sergey Laptev
CIO at StartupCraft Inc
Sergey is a mobile applications expert, system architect and CIO at StartupCraft Inc. Sergey has been living with code in mind for the past 9 years. He holds a degree in Applied Informatics and Software Engineering. His main focus lies in mobile applications as he is a React Native guru. He constantly improves his skills experimenting with bleeding edge technologies, architecture patterns and always follows the latest trends.
1111B S Governors Ave STE 7041, Dover, DE 19904, USA
© 2024 StartupCraft Inc. All rights reserved.
This website uses cookies. By staying here you accept and agree using it.
Review privacy policy