Flutter: Build and Deploy Android apps using GitHub Actions

image

Аnton Muntianu

image

Introduction

GitHub Actions is a continuous integration and continuous delivery (CI/CD) platform that allows you to automate your build, test, and deployment pipeline.

Our focus will be on building and deploying an Android App.
We will use a machine and environment that GitHub provides us by default.

By the end, you will know how to:

Let’s begin with a basic workflow.

New Workflow

GitHub Actions can be added to your existing Repository. Still, we will work with a new Flutter project, which has its first commit pushed to a fresh Repository.

GitHub Actions are enabled by default, you can start right away by adding a workflow file.
1. Create a .github/workflows directory at the root of your project
2. In the .github/workflows directory, create a file named build.yml
3. Copy the following instructions into the build.yml
4. Change to branch name from “main” if you have a different one
5. Change the Flutter version to the one you use in your project

 # Name of the workflow
name: Build

 # Controls what will trigger the workflow.
 # Change it to your needs.
 on:
 # A new push to the "main" branch. 
  push:
   branches: [ "main" ]
 # A new pull request to the "main" branch.
  pull_request:
   branches: [ "main" ]
 
 # Allows to trigger the workflow from GitHub interfaces.
   workflow_dispatch:
 
# A single workflow can have multiple jobs.
jobs: 
 # 'A new job is defined with the name: "build_android" 
 build_android:
   # Defines what operating system will be used for the actions.
   # For android, we will use Linux GitHub-Hosted Runner.
   runs-on: ubuntu-22.04
   # Defines what step should be passed for successful run
   steps:
     # Checkout to the selected branch
     - name: Checkout
       uses: actions/checkout@v3
     # Download and install flutter packages
     - name: Install Flutter
       uses: subosito/flutter-action@v2
       with:
         # Define which stable flutter version should be used
         flutter-version: "3.7.0"
         channel: 'stable'
         # Enables cache for flutter packages              
         # Speed up the process
         cache: true
     # Get Flutter project dependencies
     - name: Get dependencies
       run: flutter pub get

GitHub workflows include jobs that work via steps one by one.
We use GitHub-Hosted Ubuntu runner because it is the most cost-efficient way to build Android apps. You can read more about GitHub Pricing here.
If you want to build on your device or some other dedicated one, you can use Self-Hosted Runners.

So, you can push a new commit to the branch and look for the results in your repository’s Actions tab.

Press on your first workflow, in my case it’s called Build #1Next, press on “build_android”

Here you can watch for your workflow in progress and see its results. When the first workflow is finished, you should see something like this.

Congratulations, it works. Let’s build an unsigned App Bundle.

Creating Android Bundle

Build unsigned Bundle

The first step for our action is to build an Android App Bundle without signing. 
Before doing so, we should find and remove the release build type ‘signingConfig signingConfigs.debug’ at ./android/app/build.gradle. It should look like this:

   buildTypes {
       release {

       }
   }

Let’s add the following

R
- name: Build release app bundle
  run: flutter build appbundle

As a result, we have an unsigned app bundle at output every time a new commit has been pushed. Let’s test it out!

Another successful action.

Sign the Bundle

The next step is to sign the app bundle for further deployment.
Let’s look at the .yml syntax for this case.

- name: Sign App Bundle
  uses: r0adkll/sign-android-release@v1   
  id: sign_app
  with:
   releaseDirectory: build/app/outputs/bundle/release/
   signingKeyBase64: ${{ secrets.ANDROID_KEYSTORE_FILE_BASE64 }}
   alias: ${{ secrets.ANDROID_SIGNING_KEY_ALIAS }}         
   keyStorePassword: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}             
   
   keyPassword: ${{ secrets.ANDROID_SIGNING_KEY_PASSWORD }}

To sign an app bundle we require 4 values. To generate them, use the following command:
Note: keytool is a part of the Java package.

sudo keytool -genkey -v -keystore <your_name>.keystore -alias <your_name> -keyalg RSA -keysize 2048 -validity 10000

As a result, we have a new file, fluttergithubactionsexample.keystore generated and we already know 3 values: 

  • Alias: fluttergithubactionsexample
  • keyStorePassword: password1
  • keyPassword: password1

We need the keystore file encoded as base64. To create a new file with base64 encoded string use the macOS command:

base64 -i ./<your_name>.keystore -o ./keystore-base64.txt

To add new secrets to your repository, go to its webpage > Settings > Secrets and variables > Actions and add the 4 secrets by pressing New repository secret button.

Make sure you have removed all the spaces when you add them!

Once you have added the secrets, let’s try to run actions and test the signing process.

Deployment

At this current stage, we have signed app bundle and the last step is to deploy it to Play Store. 
Let’s look at the step.

- name: Upload to Play Store (Internal Testing)
  uses: r0adkll/upload-google-play@v1.0.18
  with:
         serviceAccountJsonPlainText: ${{ secrets.ANDROID_SERVICE_ACCOUNT_JSON }} 
    packageName: com.testgroup.flutter_github_actions_android                           releaseFiles: ${{steps.sign_app.outputs.signedReleaseFile}}                         mappingFile: ./build/app/outputs/mapping/release/mapping.txt
    track: internal

We should specify the correct package name of the app. To do so you can change the applicationId value at ./android/app/build.gradle.

To deploy the Android app, you need a service account JSON. Let’s create a new service account. We should use Google Cloud Console. Choose a project if you already have one (for example, if you have set up Firebase for it previously) or create a new one. Next, press Create Service Account button, choose a name, I will call it GitHub Actions, give it Service Account User role, press Create.

You have to add a new key to the service account, the JSON will be downloaded, add its data to GitHub Secrets as ANDROID_SERVICE_ACCOUNT_JSON

You also need to enable Google Play Android Developer API.

The next step is Google Play Console. You should have a Google Developer Account.
Navigate to > Users and permissions on the sidebar. There, at the Users tab press the Invite new users button. From there, you should enter the Service Account email. You should grab it from the Service account list for your project at Google Cloud Console. It will look like this:

Select Account Permissions and press Invite user button.

Before testing the GitHub action, you must manually add the first release to the Play Store internal testing.

Go to the app in the Play Store, choose the internal testing ring, and press the Create new release button. Here you need to drop your signed app bundle.
You can use the following workflow steps to download a signed app bundle from GitHub manually.

- name: Upload Signed App Bundle
  uses: actions/upload-artifact@v3
  with:
    name: signed-app-bundle
    path: ${{steps.sign_app.outputs.signedReleaseFile}}

Once the workflow is finished, you can download the bundle from the artifacts.

When you upload your bundle to the Play Store Console and confirm the first rollout, you are ready to automatically deploy with the action.Add the step to yours .yml file:

- name: Upload to Play Store (Internal Testing)
  uses: r0adkll/upload-google-play@v1.0.18  
  with:
    serviceAccountJsonPlainText: ${{ secrets.ANDROID_SERVICE_ACCOUNT_JSON }}
    packageName:com.testgroup.flutter_github_actions_android        
    releaseFiles: ${{steps.sign_app.outputs.signedReleaseFile}}        
    mappingFile: ./build/app/outputs/mapping/release/mapping.txt
    track: internal

Conclusion

Using GitHub actions as a continuous delivery tool has provided a comfortable, reliable service for building and deployment of any app. Benefits worth mentioning:

  • When you use a GitHub Repository, you already have a familiar environment for your CI/CD. You can have everything for your code in one place.
  • With a large community of GitHub you can have nearly any use case already covered with an Action from the marketplace.
  • At any moment, you can scale to your needs with Microsoft-provided servers or run all your actions on another server or your device with self-hosted runners without paying GitHub anything.

Overall, GitHub Actions helped us deliver apps in different configurations much faster on multiple platforms at a time.

Still thinking if GitHub Actions is a right choice for building
and deploying your application?

Share your idea with us, and we’ll come up with the best development solution for your case.

Get in touch today