Your One-Stop-Shop for Flutter CI on GitHub

In today's world of software development, Continuous Integration (CI) is an essential part of the development process for most fancy-pants software development teams. However, many small Flutter teams still don't enjoy even the benefits of a CI setup. This article gives a great starting point!

Your One-Stop-Shop for Flutter CI on GitHub
After reading this...

๐Ÿ’ก you will know how to create a basic CI setup
๐Ÿ”ฐ you can add coverage badges and reports to your repo automatically

In today's world of software development, Continuous Integration (CI) is an essential part of the development process for most fancy-pants software development teams. However, many hobbyists and small Flutter shops still don't enjoy even the most basic benefits of a CI setup, maybe because they feel overwhelmed, or don't really know where to start. If you don't use CI on even your most niche side projects, I promise you, there is a better way. We'll provide you with a CI starter kit (batteries included) and introduce you to a free tool we built called Dart Coverage Assistant that you will (learn to) love! Keep reading, and within 10 minutes, you can have a GitHub workflow running that

  • ๐Ÿงช Tests your code
  • ๐Ÿ”Ž Checks your formatting
  • ๐Ÿ“ Reports code coverage on your Pull Requests
  • ๐Ÿ”ฐ Creates coverage badges for your READMEs
  • ๐Ÿ˜ด Improves your sleep through peace of mind (citation needed)
๐Ÿค”
If your first thought is "Why would I need coverage, I don't even have any tests in my codebase?", this article might not be for you. But please hang tight, we have another post coming that you will find interesting!

If all of this sounds attractive to you, let's dive in!

Flutter CI Starter Pack

If you are starting from zero, there are a few things your CI workflow should do for you. I know you're smart, but why not let the computer do some of the dumb stuff for you, so you can build and ship more?

The most basic CI workflow should accomplish a few things for you:

  • Check your code for compile-time errors
  • Run your unit tests (even if you don't have any yet)

For a Flutter Repository, this can quickly be achieved by adding the following file:

name: Continuous Integration

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

jobs:
  flutter-check:
    name: Build Check
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - name: ๐Ÿ“š Checkout
        uses: actions/checkout@v4

      - name: ๐Ÿฆ Setup Flutter
        uses: subosito/flutter-action@v2
        with:
          sdk: 'stable'
          
      - name: ๐Ÿ”Ž Check for compile-time errors
        run: dart analyze .
      
      - name: ๐Ÿงช Run Tests
        run: flutter test --coverage

The absolute most basic workflow

This should be good-to-go out of the box for most repositories, but a few things are notable. If you take a look at the on section, the workflow will run on every Pull Request, and every push to the main branch. Using the Pull Request runs is a great way to make sure everything still works before you push to main. And the concurrency section will make sure that subsequent pushes cancel ongoing runs and save you a bunch of action minutes.

The next building block - Fancy Coverage Badges

After using your CI for a while, you might desire a bit more, and maybe you want to show off your progress and have some fancy-looking badges in your PRs and READMEs, to show off your 2% test coverage (๐Ÿฅฒ). We built a GitHub Action called Dart Coverage Assistant, that adds coverage reports to your repository without any extra setup.

GitHub - whynotmake-it/dart-coverage-assistant: Magically generates code coverage reports from your dart projects
Magically generates code coverage reports from your dart projects - whynotmake-it/dart-coverage-assistant

Dart Coverage Assistant Repository

Our recommended Level 2 CI workflow looks as follows:

name: Continuous Integration

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

jobs:
  flutter-check:
    name: Build Check
    runs-on: ubuntu-latest
    timeout-minutes: 10
    permissions:
      pull-requests: write
    steps:
      - name: ๐Ÿ“š Checkout
        uses: actions/checkout@v4

      - name: ๐Ÿฆ Setup Flutter
        uses: subosito/flutter-action@v2
        with:
          sdk: 'stable'
          
      - name: ๐Ÿ”Ž Check for compile-time errors and warnings
        run: dart analyze . --fatal-infos
      
      - name: ๐Ÿงช Run Tests
        run: flutter test --coverage

      - name: ๐Ÿ“Š Generate Coverage
        id: coverage-report
        uses: whynotmake-it/dart-coverage-assistant@v1
        with:
          generate_badges: pr

As you can see, not that much has actually changed from the Level 1 workflow. The main two differences, are that the third step in the workflow now also fails when any analyser infos have not been dealt with, and the aforementioned dart-coverage-assistant action has been added. This will

  • comment with a coverage report on every PR you open (it will even show you the difference in coverage that that PR makes)
  • open a PR with svg coverage badges every time your coverage changes on main
โ„น๏ธ
To enable PRs with coverage badges, you need to allow Actions to open Pull Requests in your GitHub Repository settings. If you don't want to do that, set generate_badges: none

Dart Coverage Assistant also seamlessly handles monorepos, so if you manage multiple Dart projects from one repository, you won't even have to set anything up for that to work, as long as you ran your tests with --coverage in all of the projects. And if you want to enforce a certain minimum coverage, or disallow PRs that decrease the coverage, that's possible too.

An example coverage Report generated by Dart Coverage Assistant

Bonus Level โ€“ Check Code Generation

Many Dart and Flutter projects rely on Code Generation through packages like freezed, json_annotation, and many more. This houses the danger that you push a change to a class, but forget to run the code generation, so it's associated generated files weren't updated. In some cases, this will be detected already, since it can cause compile-time errors, however, it makes a lot of sense to check it separately.

Thus, if your project uses code generation, add the following code under the Level 2 workflow:

   check_generation:
    name: Check Code Generation
    timeout-minutes: 10
    runs-on: ubuntu-latest
    steps:
      - name: ๐Ÿ“š Checkout
        uses: actions/checkout@v4

      - name: ๐ŸŽฏ Setup Dart
        uses: dart-lang/setup-dart@v1.6.4
        with:
          sdk: "stable"

      - name: ๐Ÿ”จ Generate
        run: dart run build_runner build --delete-conflicting-outputs

      - name: ๐Ÿ”Ž Check there are no uncommitted changes
        run: git diff --exit-code

This job will now fail if you committed something where the generated files are in a different state than they should be.