Automatically build your Flutter app APKs with Travis-CI
Tags: ci, apps, flutter, travis
Published: 2022-08-25 22:26:10

---
title: Automatically build your Flutter app APKs with Travis-CI
published: true
date: 2019-06-20 19:22:40 UTC
tags: ci,apps,flutter,travis
canonical_url: https://blog.samjakob.com/automatically-build-your-flutter-apps-with-travis-ci-4c1e47a5ae69
cover_image: https://blog.samjakob.com/_next/image?url=https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1628740461024%2FJmPwpenTf.png%3Fw%3D1600%26h%3D840%26fit%3Dcrop%26crop%3Dentropy%26auto%3Dcompress%2Cformat%26format%3Dwebp&w=3840&q=75
---

This article is [imported from my blog](https://blog.samjakob.com/automatically-build-your-flutter-apps-with-travis-ci-4c1e47a5ae69). Please let me know if there are any issues (broken links or grammatical errors due to missing characters) by commenting on this article.

***

When attempting to setup [Travis-CI](https://travis-ci.com/), I saw more than a few articles on running Flutter tests with Travis, but none for actually building and **obtaining** a compiled APK as we wanted.

Whenever changes are pushed to the GitHub repository a new build is automatically generated by Travis and a link to the APK on [WeTransfer](https://wetransfer.com/) is posted to a Discord channel for supporters and beta testers allowing them to comment on the new build.

In this article I'm going to comment on how we achieved this workflow, as well as giving some general advice.

## Step 1: Create Travis Configuration

Start by creating the following directory structure. You only need to include the `.travis/` directory (also in the root) if you plan to add additional build scripts to your project.

```yaml
your_project_root/
    - .travis.yml
    - .travis/
        - utils/
```

In your `.travis.yml` you need to add some basic boilerplate configuration:_I didnt set the language to Android because it didnt seem to setup the SDK correctly, plus using node as the language allowed us to easily set up additional build scripts._

```yaml
os: linux

# The Ubuntu Trusty release on Travis is known to
# have oraclejdk8 available. For some reason, we couldn't
# get this to work with other distributions/releases.
dist: trusty
jdk: oraclejdk8

# We set language to Node.js (JavaScript) for the sake
# of making creating utility scripts later on easier.
language: node_js
node_js:
  - "12"

env:
  global:
    - ANDROID_SDK_ROOT=/opt/android

sudo: required

addons:
  apt:
    # Flutter depends on /usr/lib/x86_64-linux-gnu/libstdc++.so.6 version GLIBCXX_3.4.18
    sources:
      - ubuntu-toolchain-r-test # if we don't specify this, the libstdc++6 we get is the wrong version
    packages:
      - lib32stdc++6 # https://github.com/flutter/flutter/issues/6207
      - libstdc++6
      - curl

cache:
  directories:
    - $HOME/.pub-cache
    - node_modules
```

Next, we need to setup our initial `before_script` stage. This will execute commands to setup Gradle, the Android SDK and Flutter.

```yaml
before_script:
  # Setup gradle.
  - wget https://services.gradle.org/distributions/gradle-4.10.3-bin.zip
  - unzip -qq gradle-4.10.3-bin.zip
  - export GRADLE_HOME=`pwd`/gradle-4.10.3
  - export PATH=$GRADLE_HOME/bin:$PATH

  # (Quick fix: Silence sdkmanager warning)
  - mkdir -p /home/travis/.android
  - echo 'count=0' > /home/travis/.android/repositories.cfg

  # Download and setup Android SDK tools.
  - wget https://dl.google.com/android/repository/sdk-tools-linux-4333796.zip
  - mkdir android-sdk-tools
  - unzip -qq sdk-tools-linux-4333796.zip -d android-sdk-tools
  - export PATH=`pwd`/android-sdk-tools/tools/bin:$PATH
  - mkdir -p $ANDROID_SDK_ROOT

  # This will install the Android SDK 28 using the previously installed SDK tools.
  - yes | sdkmanager --sdk_root=$ANDROID_SDK_ROOT "tools" "build-tools;28.0.3" "extras;android;m2repository" > /dev/null
  - export PATH=${ANDROID_SDK_ROOT}/tools/bin:$PATH

  # List sdkmanager packages
  # (useful when checking the logs)
  - sdkmanager --list

  # Clone Flutter
  # We clone the Flutter beta branch. You should clone whatever branch
  # you know works for building production apps.
  # If in doubt, you are advised to use the stable branch of Flutter
  # for production apps and you would do this by changing -b beta to -b stable
  # but we started the project before stable existed and whilst beta has always
  # worked reasonably well for us and we find stable is usually too outdated
  # and has too many missing framework features.
  - git clone https://github.com/flutter/flutter.git -b beta --depth 1

  # Add Flutter to the PATH environment variable.
  - export PATH=`pwd`/flutter/bin:`pwd`/flutter/bin/cache/dart-sdk/bin:$PATH
```

Finally, to get our fundamental configuration working, we of course have to actually execute `flutter build`. Which we do in the `script` stage.

```yaml
script:
  # Prints the flutter version
  # (allows you to ensure, for each build, that Flutter is set up correctly.)
  - flutter doctor -v

  # Run Flutter build
  - ./flutter/bin/flutter build apk
```

## Step 2: Upload the finished build to WeTransfer

![WeTransfer Upload Screen](https://cdn.hashnode.com/res/hashnode/image/upload/v1628740457513/iVceJnS1f.png)

Obviously, a key part of using Travis to build the compiled APK is being able to obtain the compiled APK. For this we use [WeTransfer](https://wetransfer.com/), a well-designed free (temporary) file sharing service with an API; exactly what we need.

Register an account for the [WeTransfer Public API](https://developers.wetransfer.com/) (developers.wetransfer.com) and under **My apps** , click Create new application. Enter your applications details and copy the API key.

**Create an environment variable for your key.** Go to the Travis page for your repository and click More options; in the dropdown choose Settings and under environment variables add a new environment variable called `WT_API_KEY` and paste in the key you copied previously. _Do **not** enable the option to Display value in log file, this will expose your WeTransfer API key to anyone viewing your build logs._

```yaml
after_success:
  # Export commit info
  - export AUTHOR_NAME=`git log -1 "$TRAVIS_COMMIT" --pretty="%aN"`
  - export COMMITTER_NAME=`git log -1 "$TRAVIS_COMMIT" --pretty="%cN"`
  - export COMMIT_SUBJECT=`git log -1 "$TRAVIS_COMMIT" --pretty="%s"`
  - export COMMIT_MESSAGE=`git log -1 "$TRAVIS_COMMIT" --pretty="%b"`
  # Upload to WeTransfer
  - npm install --save @wetransfer/js-sdk
  - export BUILD_OUTPUT_URL=`node ./.travis/utils/runUpload.js`
```

In order to give the file a clear name, we extract useful commit information from git (note; using the Travis commit reference.)

> `*$TRAVIS_COMMIT`\* is an automatic environment variable, exported by Travis at the start of a build. It refers to the git commit hash that is currently being built by Travis.

Then, we install the WeTransfer SDK using `npm` and call our own script:

```javascript
const APPLICATION_NAME = "ApolloTV";

const fs = require('fs');
const createWTClient = require('@wetransfer/js-sdk');

const commit = process.env.TRAVIS_COMMIT.substring(0, 6);
const jobName = process.env.TRAVIS_JOB_NUMBER;
const buildName = process.env.TRAVIS_BUILD_NUMBER;
const title = process.env.COMMIT_SUBJECT;
const author = process.env.AUTHOR_NAME;
const message = `Job: ${jobName}, Build: ${buildName}\n\n${title} (${author})`;

(async function(){
  const wtClient = await createWTClient(process.env.WT_API_KEY, {
    logger: {
      level: 'error'
    }
  });

  const appBinaryContent = await new Promise((resolve, reject) => {
    fs.readFile(
      './build/app/outputs/apk/release/app-release.apk',
      (error, data) => {
        if(error) return reject(error);
        resolve(data);
      }
    );
  });

  const transfer = await wtClient.transfer.create({
    message: message,

    files: [
      {
        name: `${APPLICATION_NAME} Build ${buildName} - ${commit}.apk`,
        size: appBinaryContent.length,
        content: appBinaryContent,
      }
    ]
  });

  // This is required; this is so you can obtain the URL in your Travis/bash scripts.
  console.log(transfer.url);
})();
```

As you can see, it just uploads the file in the Flutter build output directory and prints the URL, from there youre ready to go with your compiled APK.

If you want to stop here, you can just `echo` the URL for the APK and then youre able to download the builds.

## Step 3: Execute pre and post build web-hooks

![We have web-hooks that send a message to a channel in our Discord server when a build has started or ended.](https://cdn.hashnode.com/res/hashnode/image/upload/v1628740459500/K-Rm5aCiN.png)
_We have web-hooks that send a message to a channel in our Discord server when a build has started or ended._

We find its pretty helpful to be alerted when a new build starts and finishes, however we also wanted to give our supporters access to cutting-edge builds as soon as theyre pushed to GitHub and we can use our web-hooks to notify supporters when a new build is starting as well as when it is finished and available to download.

```yaml
before_install:
  # Execute Travis prebuild webhook.
  - ./.travis/10_prebuild.sh $WEBHOOK_URL

after_success:
  # Execute success procedure of postbuild webhook script.
  - ./.travis/40_postbuild.sh success $WEBHOOK_URL $BUILD_OUTPUT_URL

after_failure:
  # Execute failure procedure of postbuild webhook script.
  - ./.travis/40_postbuild.sh failure $WEBHOOK_URL
```

> _You may have noticed above that we have given numeric prefixes to each of our build scripts. This is because in our full configuration, we have further scripts to prepare our app build. This includes injecting a configuration into the app source code and checking translations.__You can check out our actual build configuration on the [Kamino GitHub repository.](https://github.com/ApolloTVofficial/kamino/blob/master/.travis.yml)_

In Discord, click the cog to edit a channel and then click Webhooks. Choose Create Webhook, give it a name such as Travis Build and copy the URL, then click Save.

In Travis, create an environment variable (see Step 2 for instructions on how to do this) for your web-hook URL. Again, _do **not** enable display value in log file, this will allow anyone to send messages to your Discord channel with the web-hook._

Finally, you need to create the `prebuild` and `postbuild` scripts; these should go in the `.travis` folder in the root of your project (that you created in Step 1):

```bash
#!/bin/bash
TIMESTAMP=$(date -u +%FT%TZ)

AUTHOR_NAME="$(git log -1 "$TRAVIS_COMMIT" --pretty="%aN")"
COMMITTER_NAME="$(git log -1 "$TRAVIS_COMMIT" --pretty="%cN")"
COMMIT_SUBJECT="$(git log -1 "$TRAVIS_COMMIT" --pretty="%s")"
COMMIT_MESSAGE="$(git log -1 "$TRAVIS_COMMIT" --pretty="%b")"

WEBHOOK_DATA='{
    "username": "ApolloTV (Travis)",
    "content": "A build has started.\n\n[Job #'"$TRAVIS_JOB_NUMBER"' (Build #'"$TRAVIS_BUILD_NUMBER"') '"$STATUS_MESSAGE"' ('"$AUTHOR_NAME"') - '"$TRAVIS_REPO_SLUG"'\n\n'"$COMMIT_SUBJECT"']('"$TRAVIS_BUILD_WEB_URL"')"
}'

(curl --fail --progress-bar -H Content-Type:application/json -d "$WEBHOOK_DATA" "$1" \
&& echo -e "\\n[Webhook]: Successfully sent the webhook.") || echo -e "\\n[Webhook]: Unable to send webhook."
```

```bash
#!/bin/bash
STATUS="$1"

TIMESTAMP=$(date -u +%FT%TZ)

if ["$STATUS" = "success"]; then
    EMBED_COLOR=3066993
    STATUS_MESSAGE="Passed"
elif ["$STATUS" = "failure"]; then
    EMBED_COLOR=15158332
    STATUS_MESSAGE="Failed"
fi

WEBHOOK_DATA='{
  "username": "ApolloTV (Travis)",
  "content": "@BuildNotify A new build has completed",
  "embeds": [ {
    "color": '$EMBED_COLOR',
    "author": {
      "name": "Job #'"$TRAVIS_JOB_NUMBER"' (Build #'"$TRAVIS_BUILD_NUMBER"') '"$STATUS_MESSAGE"' - '"$TRAVIS_REPO_SLUG"'",
      "url": "'"$TRAVIS_BUILD_WEB_URL"'"
    },
    "title": "'"$COMMIT_SUBJECT"'",
    "url": "'"$URL"'",
    "description": " **Build Information** : '"${COMMIT_MESSAGE//$'\n'/ }"\\n\\n"$CREDITS"'",
    "fields": [
      {
        "name": "Commit",
        "value": "'"[${TRAVIS_COMMIT:0:7}](https://github.com/$TRAVIS_REPO_SLUG/commit/$TRAVIS_COMMIT)"'",
        "inline": true
      },
      {
        "name": "Branch",
        "value": "'"[$TRAVIS_BRANCH](https://github.com/$TRAVIS_REPO_SLUG/tree/$TRAVIS_BRANCH)"'",
        "inline": true
      },
      {
        "name": "Files",
        "value": "'"[Download APK]($BUILD_OUTPUT_URL)"'"
      }
    ],
    "timestamp": "'"$TIMESTAMP"'"
  } ]
}'

(curl --fail --progress-bar -H Content-Type:application/json -d "$WEBHOOK_DATA" "$2" \
&& echo -e "\\n[Webhook]: Successfully sent the webhook.") || echo -e "\\n[Webhook]: Unable to send webhook."
```

Feel free to use and customise these scripts as you wish.