Automate Flutter app delivery to AppCenter with GitHub Actions
Tags: flutter, devops, githubactions, android
Published: 2023-10-06 22:31:42

## Introduction

In this post, you'll see how to automate the delivery of your Flutter app to the testing team. For this to work, your app must have follow these conditions:
- You're developing a [Flutter](https://flutter.dev) app;
- Your code is hosted on [GitHub](https://github.com);
- Your testing team is using [AppCenter](https://appcenter.ms).

Got it? Let's begin.

## What is Continuous Integration?

According to [Atlassian's Max Rehkopf](https://www.atlassian.com/continuous-delivery/continuous-integration), it's about **automatically running builds and tests with every change**. This speeds up development, since it is happening automatically, the developer can focus on the development of new features and leave validation and testing to automated tools.

## Ok, so what's Continuous Delivery then?

Taking inspiration from [Pittet's post](https://www.atlassian.com/continuous-delivery/principles/continuous-integration-vs-delivery-vs-deployment), CD is **automatic deployment of the code on testing and/or production environment**. It's often confused with Continuous *Deployment*, which is the automated release to the end customers.

In this post we'll focus on **Continuous Delivery**, since we'll be automating the deployment of your app to AppCenter.

## Step 1 - Setting up a Runner for GitHub Actions

If your code is hosted on GitHub, the best approach to DevOps is to write GitHub Actions. You can read more about Actions [here](https://github.com/features/actions), but everything that you need to know will be explained later on. For now, you have to setup a **Runner**.

A [runner](https://github.com/actions/runner) is where your action's jobs will be run. It can be a hosted virtual environment, or you can self-host a runner in your machine.

To register your own machine as a runner, you must go to the Settings of your repository and follow the instructions on **Settings > Actions > Runner**.
![To set up a runner, go to Settings > Actions > Runner](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aonrgg2wxfqdwfhoviix.png)

There you'll see a green button **"New self-hosted runner"**. Click on it and then follow the steps. It is simply running a few commands on you terminal, so I'll not be covering it here.

In the end, you should be running your runner and it will be shown in the same page of the Settings, such as below.
![Runners page displaying my machine as an Idle Runner](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l2cdh6ydx86l7o4hacs1.png)

> If you have any issues on this part, feel free to leave a comment below.

## Step 2 - Set up your AppCenter app

On your AppCenter app's settings, go to **Settings > App API Tokens**. There, you can create a token that'll be used on our Actions workflow. 

![AppCenter app's Settings showing the Add API Tokens page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tywy93uo5j1q9jwzv9sh.png)

Click on **"New API Token"**. You'll be asked to set up a name and access for your token. Set whatever you'd like, but the token must have **Full access**. Don't worry, only you can see this token.

When the pop-up with the token appears - such as the one below, copy the token and save it somewhere safe. You **cannot** see it again, and if you happen to lose it, you must delete it and create a new one.

![AppCenter's pop-up with the API token censored](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u1lg0bfwqfnpcs8q57im.png)

With your AppCenter API Token, go back to your repository on GitHub. On **Settings > Secrets and variables > Actions**, we'll create a Secret for our repository.

![Settings > Secrets and variables > Actions](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iv77nax84r8wqx2nd10t.png)

A **secret** is a variable that is used by our GitHub actions but is not displayed for who does not have permission. The automated workflow can use the value, but no one is able to see what it is. This enables the user to have environment variables such as AppCenter's API Token without it being compromised or leaked. For more information on secrets, refer to the [documentation](https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions).

Now, click on **"New repository secret"** and create a **APPCENTER_API_TOKEN** and paste the value of the token previously created. If you've done everything correctly, this must be what you see on your page.

![Secrets and variables page with the new created APPCENTER_API_TOKEN](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m7v0cgluygl0jy6wah80.png)

Now we can finally start creating workflows for our repository.

## Step 3 - Creating your GitHub Actions Workflow

First of all, you repository needs a **.github** folder on the root folder. Then, you create a **workflows** folder. There, we'll create a **main.yml** file. The end result should be something like this:

![Printscreen of the .github/workflows folder](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kvsxvr1gmdmthxa331o9.png)

> Note that the file doesn't have to be named `main.yml`, this is just for this example.

In the **main.yml** file, we must add some code so that it does what we want. Start by writing this:
```main.yml
name: Deploy App on AppCenter
on:
  push:
    branches:
      - main
      - 'releases/**'
```

- **name**: Self explanatory. Sets the name of this workflow.
- **on**: Defines the triggers of this workflow. On our case, we want this workflow to be triggered on every `push` that happens on the `main` branch, or any other branch that starts with `releases/`, such as `releases/1.0.0`, `releases/hotfix-login`, etc.

> For more information on `on` tag, see the [documentation](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#onpushpull_requestpull_request_targetpathspaths-ignore).

But still, it's not doing anything. So now we need to add [Jobs](https://docs.github.com/en/actions/using-jobs/using-jobs-in-a-workflow). Jobs are what make up workflows, and are the steps you want to execute.

For this post, we'll set up these steps:
- Set up Flutter;
- Set up AppCenter CLI;
- Build the app's package;
- Deploy the package to AppCenter.

> In this post, only Android will be covered. Follow me to know when the post for iOS is released!

## Step 3.1 - Set up Flutter

Now that you have your `main.yml` file configured, we can add `jobs` to achieve what we want. Start by adding a `jobs` and a "Set up Flutter" step, such as the code below.

```main.yml
jobs:
  setup-flutter:
    name: Setup Flutter
    runs-on: self-hosted
    steps:
      - name: Check out repository code
        uses: actions/checkout@v3
      - name: Install jq
        uses: dcarbone/install-jq-action@v2.0.2
      - uses: subosito/flutter-action@v2
        with:
          channel: master
          architecture: x64
      - run: flutter pub get
```

Let's take this apart:
- **jobs**: Defines jobs that must be run for this workflow. These can be run in parallel or sequentially. By default, they are run in parallel. More information later.
- **runs-on**: Defines on what kind of Runner this job can be run. Since we've defined our self-hosted runner in the previous steps, we just add this value.
- **steps**: Defines the steps for this job. Each will be run sequentially.

If you pay attention, we already created 3 steps. They have two tags: **name** and **uses**. Name is self explanatory.

**Uses** defines what action is used on this step. In the first case, **actions/checkout@v4** is used. This format says what repository and action is used. Below is the list of actions used:

- **actions/checkout@**: Checks out the code in the runner. This makes sure that the workflow is being run on updated code.
- **dcarbone/install-jq-action**: This actions installs `jq` on the runner machine. It's a lightweight command-line JSON processor. For more information, check it's [page](https://jqlang.github.io/jq/). This is not necessary to build Flutter, but it's used in other actions later on.
- **subosito/flutter-action**: This action installs and runs Flutter commands. It's what we'll use to get dependencies and build our package. It can also be used to run our tests and check for coverage, but it's not being implemented in this guide. This action has a `run` parameter, which defines the specific command we want to be run.

If you pay attention, all these actions are actually public repositories that we can use. GitHub Actions have a [public marketplace](https://github.com/marketplace?type=actions) that you can add actions that have been developed by the community. To check what's the code is doing, you can go to the repository and check it. It's best to see what it's doing to make sure that you're not using anything with a specific vulnerability.

So to summarize, we've set up a workflow that on every push that happens on **master** or **release/\*\*** installs Flutter and the Pub dependencies on the runner.

Now, we can add the next step.

## Step 3.2 - Set up AppCenter CLI

This step is going to be easier since we already know what we're doing. 

```main.yml
  setup-appcenter-cli: # must have node and npm installed!
      name: Setup AppCenter CLI
      runs-on: self-hosted
      steps:
      - name: Install appcenter-cli
        uses: charliealbright/appcenter-cli-action@v1.0.1
        with:
          token: '${{secrets.APPCENTER_API_TOKEN}}'
          command: 'appcenter help'
```

We now have a new tag to go over:
- **with**: This sets some parameters to be given to the action as an input. 
In this case, we add `token` and `command`. If you pay attention, we're using `secrets.APPCENTER_API_TOKEN`.

**Secret** is an object that contains every variable we've set in the repository's secret. Since we've set APPCENTER_API_TOKEN, this token can now be used in this action. Again, **make sure that you know what your actions are doing**.
We then add `"${{"` and `"}}"` around the variable so that Actions knows that it must change this value to the one set on the secrets.

So now we have two steps: setting up Flutter and AppCenter CLI.

## Step 3.3 - Call Deploy Android job on the main workflow

We now can use some of the superpowers of GitHub Actions - creating different jobs on different files. Add this specific job below `setup-appcenter-cli`:

```main.yml
  deploy_android:
    name: Deploy Android
    needs: [setup-flutter, setup-appcenter-cli]
    uses: ./.github/workflows/deploy_android.yml
    with:
      file: './build/app/outputs/apk/release/app-release.apk'
      name: 'AlvaroBarrosC/GithubAction-Android'
```
We now have two tags to go over:
- **needs**: This sets some requirements for this job. In our case, it needs Flutter and AppCenter CLI to be set up. This also means that this Deploy job will be run sequentially.
- **uses**: We've already seen this tag, but now we're giving a local path. This will use a `deploy_android.yml` file that we've created on `workflows` folder. So let's create the file.

As described previously, the **with** tag adds some inputs. In this case, we've added **file** and **name**.

- **file**: This must be the path to the file that is generated by the Flutter `build` command. Above is the default output directory, but this can be changed.
- **name**: This one is the username, slash and the app’s name on AppCenter. Mine is `AlvaroBarrosC` and `GithubAction-Android`, but you must change it to your own.

## Step 4 - Create Deploy Android workflow

As said in the previous step, create the file on `.github/workflow/deploy_android.yml`, the same folder where your `main.yml` file is located. Then, you can paste this code:

```deploy_android.yml
name: Deploy Android App on AppCenter
on: 
  workflow_call:
    inputs:
      file:
        description: 'The path to the file to be released'
        required: true
        type: string
      name:
        description: 'The name of the app'
        required: true
        type: string
      group:
        description: 'The group that will have access to the version released'
        required: false
        type: string
        default: '"Collaborators"'
jobs:
  build:
    name: Build .apk file
    runs-on: self-hosted
    steps:
      - run: flutter build apk --release --verbose
  Deploy:
    name: Deploy file to AppCenter
    needs: [build]
    runs-on: self-hosted
    steps:
      - name: AppCenter CLI Action
        uses: charliealbright/appcenter-cli-action@v1.0.1
        with:
          token: ${{secrets.APPCENTER_API_TOKEN}}
          command: 'appcenter distribute release -f ${{inputs.file}} --app ${{inputs.name}} --group ${{inputs.group}}'
```
You now can see how the inputs are defined on the top of the file. They have some metadata, such as `required`, `type` and `description`. You can also see them being used on the `command` parameter of the AppCenter CLI Action. By using `${{inputs.[field]}}``, you place the value given when the workflow was called.

We can now make a push on any change in the affected branches and see the workflow being run. Make sure your runner is running.

## Step 5 - Deploy!

If you followed every step correctly, whenever you go to your Repository’s **Actions** tab, you can see your previous runs there. Also, you can debug and see the log of the steps.

![Printscreen showing available workflows](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qrfofle0oxvvobmbfzt8.png)

> Note that iOS deployment was not covered on this post.

![The execution of the workflow, step by step](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tqu6u75r9qx4g79whz3z.png)

## Thanks for reading!

If you've missed any of the steps or is encountering any problem, you can check the code on [this repo](https://github.com/AlvBarros/flutter-appcenter-github-action). Feel free to comment on this post with any questions that you have.