Skip to main content

Tutorial 9: Flutter Deployment with GitHub Actions and Microsoft App Center

Platform-Based Programming (CSGE602022) — Organized by the Faculty of Computer Science Universitas Indonesia, Odd Semester 2023/2024


Learning Objectives

After completing this tutorial, students are expected to be able to:

  • Understand the concepts of continuous integration and continuous deployment.
  • Understand the concepts and usage of GitHub Actions.
  • Understand the concepts and usage of Microsoft App Center.
  • Implement continuous integration and continuous deployment on a Flutter application using GitHub Actions and Microsoft App Center.
  • Implement GitHub Actions to automatically build and release Flutter applications.

Introduction to CI/CD

CI/CD, which stands for Continuous Integration and Continuous Deployment, is an essential concept in software development related to GitHub Actions. This concept provides a way to automate and improve the quality and speed of software development.

Continuous Integration (CI) focuses on continuously integrating code changes into a shared repository by team members. When a developer makes changes to the code and submits it to the repository (as we do with GitHub), the CI system will automatically run a series of tests and verifications to ensure that the changes do not break or disrupt existing functionality. In other words, CI helps identify issues earlier in the development cycle.

Continuous Deployment (CD), on the other hand, involves automating the release of code changes that have passed the CI process to a production or testing environment. When code changes are deemed safe after passing a series of CI tests, CD allows for the automatic deployment of those changes to servers or other environments without manual intervention. This speeds up the development process and improves the speed of responding to business requirements.

When using GitHub Actions in CI/CD, every time there is a change in the repository, GitHub Actions can trigger a CI workflow to run tests and verifications. If everything is successful, a CD workflow can be activated to release those changes to the production or testing environment.

By using CI/CD, development teams can ensure that changes made do not compromise the quality or performance of the application. It also accelerates product release time and improves efficiency in managing the software development lifecycle overall.

Here is a visualization of the CI/CD flow diagram:

Diagram CI/CD

Introduction to Github Actions

GitHub Actions is a feature provided by GitHub to enable automation in the software development cycle. In other words, GitHub Actions allows us to create and customize automated workflows to perform specific tasks whenever there is a change in a GitHub repository.

These workflows can be set to execute various actions or scripts automatically, such as running tests, building applications, or releasing new versions. The goal is to help developers automate these processes, allowing them to focus on writing code and developing features without being overly concerned with administrative steps.

For example, when there is a code change in a GitHub repository, GitHub Actions can automatically run the specified workflow. This workflow may include steps such as testing whether the change does not break existing functionality, building a new application, and even releasing a new version if necessary.

It's important to note that GitHub Actions utilizes a special configuration file (usually named .github/workflows/filename.yml) in the repository. This file contains a description of the steps to be executed by GitHub Actions.

With GitHub Actions, collaboration in software development can become more efficient because many tasks can be automated. This provides additional flexibility to developers and teams to customize their workflows according to project needs.

Here is an example of a simple workflow that we can use to build and test a JavaScript-based application using the Yarn package manager.

name: Build and test

on:
push:
branches:
- main

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: yarn install
- name: Build
run: yarn build

test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 16
- name: Install dependencies
run: yarn install
- name: Run tests
run: yarn test

Introduction to Microsoft App Center

Microsoft App Center is a cloud service that provides a variety of features to facilitate the build, test, release, and monitoring processes of applications. This service can be used for various platforms such as Android, iOS, Windows, macOS, and more. In this tutorial, we will use this service to automatically build and release Flutter applications.

Microsoft App Center provides many cool features for free, such as continuous integration, UI testing, continuous delivery, detailed crash and error reports from the application, and Analytics features. This time, we will not use all the available features because Flutter is not officially supported by App Center yet. Currently, the languages and frameworks that are officially supported are Kotlin, Java, React Native, Xamarin, and Unity. In this tutorial, we will focus on creating and releasing applications on App Center only. For more details, you can read the App Center documentation.

Tutorial: App Center Basic Configuration

In this tutorial, you will deploy your Flutter app that you have created in the previous tutorials to the App Center.

  1. Create an App Center account using your GitHub.

    Visual Studio App Center Registration Page

  2. Create a new organization by clicking Add new -> Add new organization. Fill out the desired organization name.

    Add Organization

  3. Create a new application slot by clicking the Add app button.

    Add App

  4. Fill out the application name with Shopping List. You can skip the release type. Choose Android as the OS and Java / Kotlin as the platform.

    App Registration

  5. Open the Distribute menu and select Groups.

    Add Group

  6. Create a new group by clicking the Add Group button. Set the group name as Public and give it pulic access by selecting the Allow public access toggle button. Click the Create Group button to create the group. This steps ensure that our released APK can be publicly accessed.

    Create Public Group

At this point, you have successfully configure your App Center. Next, you will configure the scripts for deployment and signing your Flutter app.

Tutorial: Signing Your Flutter App

Before publishing your app on the App Center, your Flutter app must be signed using a key to ensure the validity of your app. So, we will create a key and set up a CI/CD script to automatically sign your app.

  1. Create a keystore.

    For Mac OS and Linux user, run this command in the Terminal.

    keytool -genkey -v -keystore ~/release-keystore.jks -keyalg RSA -keysize 2048 -validity 10000 -alias release

    For Windows user, run this command in the Command Prompt.

    keytool -genkey -v -keystore %userprofile%\release-keystore.jks -storetype JKS -keyalg RSA -keysize 2048 -validity 10000 -alias release

    This command is used to create a keystore file with name release-keystore.jks in your home directory with release as an alias. Move that file to your project's root folder. If your Terminal or Command Prompt does not recognize the keytool command, please follow this guide.

  2. Add the following patterns to the .gitignore file in your project's root directory. These patterns ensure that the keystore file is not tracked by git. This step is necessary because the keystore file is confidential and should be treated like a password.

    # Remember to never publicly share your keystore.
    # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
    *.keystore
    *.jks
  3. Open the /android/app/build.gradle file and go to the buildTypes section.

    buildTypes {
    release {
    // TODO: Add your own signing config for the release build.
    // Signing with the debug keys for now,
    // so `flutter run --release` works.
    signingConfig signingConfigs.debug
    }
    }

    Change that part as follows.

    signingConfigs {
    release {
    storeFile file("../../release-keystore.jks")
    storePassword = "$System.env.KEY_PASSWORD"
    keyAlias = "release"
    keyPassword = "$System.env.KEY_PASSWORD"
    }
    }
    buildTypes {
    release {
    signingConfig signingConfigs.release
    }
    }

At this point, you have configured the signing for your app. Next, you will modify the Github Actions script and create a script to build the app file on App Center.

Tutorial: Creating GitHub Actions Scripts

  1. Generate a base64 string as a representation of the keystore file. This string will be stored as an environment variable later.

    Run the command openssl base64 -in release-keystore.jks on your project's root directory to create a base64 string. For now, save the generated string.

    Here is the example of the command result.

    Example Openssl

  2. Create three repository secrets for your GitHub repository.

    i. GH_TOKEN with the value of your Github Personal Access Token. This will be used for the automated release.

    ii. KEY_JKS with the value of the base64 string you have generated before.

    iii. KEY_PASSWORD with the value of the password you used when you create the keystore file.

  3. Create a directory called .github/workflows on your project's root directory (if not yet created).

  4. Create three new files inside the .github/workflows directory.

    We assume that the staging branch is used to store your application code kode before release and the main branch is used to release your application.

    i. staging.yml. This script checks your codebase to ensure that the staging branch does not produce any error when the command flutter analyze is called. This script only triggered if there is any commit in the staging branch.

    name: Staging

    # Controls when the workflow will run
    on:
    # Triggers the workflow on push or pull request events but only for the develop branch
    push:
    branches: [staging]
    pull_request:
    branches: [staging]

    # A workflow run is made up of one or more jobs that can run sequentially or in parallel
    jobs:
    # This workflow contains a single job called "build"
    build:
    name: Analyze
    # The type of runner that the job will run on
    runs-on: ubuntu-latest
    steps:
    - name: Checkout the code
    uses: actions/checkout@v4

    - name: Setup Java
    uses: actions/setup-java@v3
    with:
    distribution: 'zulu'
    java-version: '11'

    - name: Setup Flutter
    uses: subosito/flutter-action@v2
    with:
    channel: 'stable'

    - name: Get packages
    run: flutter pub get

    - name: Analyze
    run: flutter analyze

    ii. pre-release.yml. This script is used to check that the application build process can be run without error. If there is no error, the APK file can be accessed as an artifact. This script only triggered when there is a pull request created from the staging branch to the main branch.

    name: Pre-Release

    # Controls when the workflow will run
    on:
    # Triggers the workflow on pull request events but only for the main branch
    pull_request:
    branches: [main]

    # A workflow run is made up of one or more jobs that can run sequentially or in parallel
    jobs:
    # This workflow contains a single job called "Build and Pre-Release APK"
    releases:
    name: Build and Pre-Release APK
    # The type of runner that the job will run on
    runs-on: ubuntu-latest
    steps:
    - name: Checkout the code
    uses: actions/checkout@v4

    - name: Setup Java
    uses: actions/setup-java@v3
    with:
    distribution: 'zulu'
    java-version: '11'

    - name: Setup Flutter
    uses: subosito/flutter-action@v2
    with:
    channel: 'stable'

    - name: Get packages
    run: flutter pub get

    - name: Generate Java keystore
    env:
    KEY_JKS: ${{ secrets.KEY_JKS }}
    run: echo "$KEY_JKS" | base64 --decode > release-keystore.jks

    - name: Build APK
    env:
    KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
    run: flutter build apk --split-per-abi

    - name: Pre-release APK by uploading it to Artifacts
    uses: actions/upload-artifact@v3
    with:
    name: APKS
    path: build/app/outputs/flutter-apk/*.apk

    iii. release.yml. This script is used to build your application and release it as Releases on your GitHub repository. This script only triggered when there is a commit created in the main branch.

    # This is a basic workflow to help you get started with Actions
    name: Release

    # Controls when the workflow will run
    on:
    # Triggers the workflow on push events but only for the main branch
    push:
    branches: [main]

    # A workflow run is made up of one or more jobs that can run sequentially or in parallel
    jobs:
    # This workflow contains a single job called "Build and Release APK"
    releases:
    name: Build and Release APK
    # The type of runner that the job will run on
    runs-on: ubuntu-latest
    steps:
    - name: Checkout the code
    uses: actions/checkout@v4

    - name: Get version from pubspec.yaml
    id: version
    run: echo "::set-output name=version::$(grep "version:" pubspec.yaml | cut -c10-)"

    - name: Setup Java
    uses: actions/setup-java@v3
    with:
    distribution: 'zulu'
    java-version: '11'

    - name: Setup Flutter
    uses: subosito/flutter-action@v2
    with:
    channel: 'stable'

    - name: Get packages
    run: flutter pub get

    - name: Generate Java keystore
    env:
    KEY_JKS: ${{ secrets.KEY_JKS }}
    run: echo "$KEY_JKS" | base64 --decode > release-keystore.jks

    - name: Build APK
    env:
    KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
    run: flutter build apk --split-per-abi

    - name: Get current date
    id: date
    run: echo "::set-output name=date::$(TZ='Asia/Jakarta' date +'%A %d-%m-%Y %T WIB')"

    - name: Release APK
    uses: ncipollo/release-action@v1
    with:
    allowUpdates: true
    artifacts: "build/app/outputs/flutter-apk/*.apk"
    body: "Published at ${{ steps.date.outputs.date }}"
    name: "v${{ steps.version.outputs.version }}"
    token: ${{ secrets.GH_TOKEN }}
    tag: ${{ steps.version.outputs.version }}
  5. Save those files and push it to the GitHub repository. Check whether your app has successfully built and released by GitHub Actions automatically.

If your app have successfully built and released, congrats! At this point, we have created a workflow in GitHub. Next, we will create a new script to build your app on the App Center and do more configuration on the App Center.

Below is the structure of your Flutter app after doing this tutorial.

Project structure

Tutorial: Adding CI/CD Scripts for App Center

In this section, we will add continuous integration and continuous delivery (CI/CD) scripts to the Flutter application repository so that App Center can automatically build and generate the APK application files.

  1. Open the /android/app folder.

  2. Create a new file named appcenter-post-clone.sh and fill the file with the following code.

    #!/usr/bin/env bash
    # Place this script in project/android/app/

    cd ..

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

    cd ..
    git clone -b beta https://github.com/flutter/flutter.git
    export PATH=`pwd`/flutter/bin:$PATH

    flutter channel stable
    flutter doctor

    echo "Installed flutter to `pwd`/flutter"

    # export keystore for release
    echo "$KEY_JKS" | base64 --decode > release-keystore.jks

    # build APK
    # if you get "Execution failed for task ':app:lintVitalRelease'." error, uncomment next two lines
    # flutter build apk --debug
    # flutter build apk --profile
    flutter build apk --release

    # copy the APK where AppCenter will find it
    mkdir -p android/app/build/outputs/apk/; mv build/app/outputs/apk/release/app-release.apk $_
  3. Open the /android/.gitignore file and modify it as follows. This is done to ensure that App Center recognizes the application as an Android application.

    # add comment for app center
    # gradle-wrapper.jar
    # /gradlew
    # /gradlew.bat
    /.gradle
    /captures/
    /local.properties
    GeneratedPluginRegistrant.java

    # Remember to never publicly share your keystore.
    # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
    key.properties
    **/*.keystore
    **/*.jks
  4. Save the file and push it to the repository. Make sure that both this script and the updated .gitignore have been pushed to the main branch.

Once the scripts are created, we will configure the application on App Center to enable automatic building and releasing.

Tutorial: Further Configuration on App Center

  1. Open the App Center website and navigate to your application project.

    Step 1

  2. Go to the Build menu and select GitHub as the repository service. Choose your project group's application repository.

    Step 2

  3. Open your main branch (main or master, as applicable) and click the Configure button.

    Step 3

  4. Follow the settings below.

    Step 4-1 Step 4-2 Step 4-3

    Note:

    • Do not forget to replace KEY_JKS and KEY_PASSWORD with their actual values.

    • Remember to create a Public group for public application distribution.

    • Copy the build badge link in Markdown format and paste it into your README.md.

      Step 4-4

      Step 4-5

  5. Click the Save & Build button to save the configuration and initiate the initial build process.

    You can check the public link for the application publication on App Center through the Distribute -> Groups -> Public menu.

    Publication Link

    Here is an example of the App Center interface when users access the public link for the application publication.

    Installation Window

  6. Copy the public link from the application publication and paste it into your README.md.

Closing Words

Congratulations, you have successfully deployed your Flutter application to App Center. You can check the deployed application by downloading the APK file from App Center and installing it on your smartphone.

And with that, we officially conclude the tutorial for PBP odd semester 2023-2024! Thank you for following and completing all the tutorials for PBP odd Ssemester 2023-2024. We would like to express appreciation to all students who have participated and contributed to this course. We observed the effort and dedication you have shown in facing the challenges of multiplatform application development in this course.

Throughout the lab sessions and assignments, we have explored the fundamental concepts and principles underlying web and mobile application development using Django and Flutter. You have learned about the architectures, features, and tools that can help in building robust and responsive applications on both of these platforms.

We hope that all the tutorials and assignments provided have given you a deeper understanding of the potential and challenges in multiplatform application development and equipped you with valuable skills applicable to your careers as software developers.

However, learning does not end here. The development world is rapidly evolving, and it is important to stay up-to-date with the latest developments in this industry. The teaching team encourages you to continue learning and keep your skills relevant by reading other references, taking advanced courses, and participating in real-world projects.

In conclusion, remember that multiplatform application development is an exciting and promising field. Keep exploring and innovating, and we are confident that you have a bright future as software developers. Thank you, and may you succeed in your journey!

またね~ 頑張ってね!

Contributors

  • Fadhlan Hasyim
  • Muhammad Vicky Maulana
  • Stenly Yosua Saputra
  • Steven Yosua Saputra
  • Aidah Novallia Putri (EN Translator)
  • Bonaventura Galang (EN Translator)
  • Ferry (EN Translator)

Credits

This tutorial was developed based on a blog entry written by Muhammad Athallah. All tutorials and instructions provided in this repository are designed in such a way that students taking the Platform-Based Programming course can complete the tutorials during lab sessions.