Skip to content
compiler.dev

Flutter GitHub Actions Workflow Template

Last updated

This is a working .github/workflows/ci.yml for a Flutter app. It installs Flutter with caching, resolves packages, runs flutter analyze and flutter test, and builds a debug Android APK in a second job.

The workflow

name: CI

on:
  push:
    branches: [main]
  pull_request:

permissions:
  contents: read

concurrency:
  group: ci-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
  cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
  test:
    runs-on: ubuntu-latest
    timeout-minutes: 20
    steps:
      - uses: actions/checkout@v4

      - uses: subosito/flutter-action@v2
        with:
          channel: stable
          cache: true

      - run: flutter pub get
      - run: dart format --output=none --set-exit-if-changed .
      - run: flutter analyze
      - run: flutter test --coverage

  android:
    needs: test
    runs-on: ubuntu-latest
    timeout-minutes: 30
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: 17

      - uses: subosito/flutter-action@v2
        with:
          channel: stable
          cache: true

      - uses: gradle/actions/setup-gradle@v4
        with:
          cache-read-only: ${{ github.ref != 'refs/heads/main' }}

      - run: flutter pub get
      - run: flutter build apk --debug

What the non-obvious parts do

  • subosito/flutter-action@v2 downloads the Flutter SDK. cache: true caches the SDK itself, so later runs skip the large download. It does not cache pub packages by default; flutter pub get reads from the pub cache on the runner, which is usually quick. For heavy dependency trees, add an actions/cache step for ~/.pub-cache keyed on pubspec.lock.
  • channel: stable floats with the latest stable. For repeatable builds pin flutter-version: 3.x.x (use the version your team develops on) with channel: stable.
  • Two jobs. Analysis and tests are fast and give early feedback. The Android build is heavier, so it waits with needs: test and does not run if tests fail. Remove needs to run in parallel for less waiting at higher cost.
  • dart format ... --set-exit-if-changed fails when files are not formatted, without writing changes.
  • setup-gradle caches the Gradle files that the Android build downloads. See Gradle caching.
  • Java 17. Recent Android Gradle plugins need at least Java 17. Match your project's requirement.

Variants

iOS builds

Add a job on macos-latest with flutter build ios --no-codesign. macOS minutes are costly, so run it on main only, not every pull request. See macOS runners for iOS CI.

Release builds and signing

Store the keystore as a secret, decode it into a file at build time, and run flutter build appbundle. Never commit keystores. Upload the result as an artifact.

Monorepo with melos

Run dart pub global activate melos and melos bootstrap, then melos run test. Add a paths: filter to skip unaffected packages.

Integration tests

Run flutter test integration_test on an emulator with reactivecircus/android-emulator-runner@v2. Emulators are slow and benefit from test sharding.

Deploy

Add a Fastlane or Firebase App Distribution job on main only.

Run it on faster machines

Change one line on each Linux job:

    runs-on: [self-hosted, Linux, compiler-ci]

The jobs run on a fresh 8 vCPU machine from compiler.dev, with automatic fallback to your original runner. Android builds use all cores. See speed up Flutter on GitHub Actions and pricing. macOS runners for iOS are in early access at compiler.dev.

FAQ

Do I need to cache the pub cache?

Not for small apps. Add it if flutter pub get takes more than about 30 seconds.

Why does the first run take so long?

It downloads the Flutter SDK, Android SDK components and Gradle dependencies. Later runs reuse caches.

Can I test several Flutter versions?

Yes. Use a matrix build over flutter-version.

Should analyze fail on warnings?

Use flutter analyze --fatal-infos for the strictest setting, or the default, which fails on errors and warnings.