← Articles

Setting up GitHub Actions for Flutter from scratch

By Ann Tech · 12 October 2024

GitHub Actions automates your Flutter CI — running tests, building artifacts, and deploying. Here is a complete setup from scratch.

Basic test workflow

Create .github/workflows/test.yml:

name: Tests

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

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

      - uses: subosito/flutter-action@v2
        with:
          flutter-version: '3.24.0'
          channel: 'stable'
          cache: true  # Cache Flutter SDK between runs

      - name: Get packages
        run: flutter pub get

      - name: Analyze
        run: flutter analyze

      - name: Run tests
        run: flutter test --coverage

      - name: Upload coverage
        uses: codecov/codecov-action@v4
        with:
          file: coverage/lcov.info

Android build

name: Build Android

on:
  push:
    branches: [main]

jobs:
  build-android:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

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

      - uses: subosito/flutter-action@v2
        with:
          flutter-version: '3.24.0'
          cache: true

      - run: flutter pub get

      - name: Build App Bundle
        run: |
          flutter build appbundle \
            --flavor production \
            -t lib/main.dart \
            --dart-define=ENVIRONMENT=production \
            --dart-define=API_BASE_URL=${{ secrets.PROD_API_URL }}

      - name: Upload artifact
        uses: actions/upload-artifact@v4
        with:
          name: app-bundle
          path: build/app/outputs/bundle/productionRelease/app-production-release.aab
          retention-days: 7

iOS build (requires macOS runner)

build-ios:
  runs-on: macos-14
  steps:
    - uses: actions/checkout@v4

    - uses: subosito/flutter-action@v2
      with:
        flutter-version: '3.24.0'
        cache: true

    - run: flutter pub get

    - name: Install CocoaPods
      run: cd ios && pod install

    - name: Build IPA
      run: |
        flutter build ipa \
          --flavor production \
          -t lib/main.dart \
          --export-options-plist=ios/ExportOptions.plist

    - uses: actions/upload-artifact@v4
      with:
        name: ipa
        path: build/ios/ipa/

Signing on Android

Store your keystore as a base64 secret:

base64 -i your.keystore | pbcopy  # Copy to clipboard

Add secrets in GitHub → Repository Settings → Secrets:

  • KEYSTORE_BASE64
  • KEYSTORE_PASSWORD
  • KEY_ALIAS
  • KEY_PASSWORD
- name: Decode keystore
  run: echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 --decode > android/app/keystore.jks

- name: Build signed App Bundle
  run: |
    flutter build appbundle \
      --flavor production \
      -t lib/main.dart
  env:
    KEYSTORE_PATH: keystore.jks
    KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
    KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
    KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}

android/app/build.gradle.kts:

signingConfigs {
  create("release") {
    storeFile = file(System.getenv("KEYSTORE_PATH") ?: "keystore.jks")
    storePassword = System.getenv("KEYSTORE_PASSWORD")
    keyAlias = System.getenv("KEY_ALIAS")
    keyPassword = System.getenv("KEY_PASSWORD")
  }
}

Matrix builds across Flutter versions

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        flutter-version: ['3.22.0', '3.24.0']
    steps:
      - uses: subosito/flutter-action@v2
        with:
          flutter-version: ${{ matrix.flutter-version }}
      # ...

Caching pub packages

- name: Cache pub packages
  uses: actions/cache@v4
  with:
    path: ~/.pub-cache
    key: ${{ runner.os }}-pub-${{ hashFiles('**/pubspec.lock') }}
    restore-keys: ${{ runner.os }}-pub-

Alternatively, subosito/flutter-action@v2 has a cache: true option that handles this automatically.

Deploying to Firebase App Distribution

- name: Upload to Firebase App Distribution
  uses: wzieba/Firebase-Distribution-Github-Action@v1
  with:
    appId: ${{ secrets.FIREBASE_APP_ID }}
    token: ${{ secrets.FIREBASE_TOKEN }}
    groups: testers
    file: build/app/outputs/bundle/stagingRelease/app-staging-release.aab

Common pitfalls

Not caching Flutter SDK. A cold Flutter SDK download adds 2-3 minutes to every build. Always use cache: true in subosito/flutter-action.

Running iOS builds on ubuntu runners. iOS builds require Xcode, which is only on macOS. Using an ubuntu runner for flutter build ipa fails immediately. Use runs-on: macos-14 for iOS.

Secrets not available in forked PRs. GitHub Actions doesn't pass secrets to workflows triggered by pull requests from forks. This is a security measure. Test builds from forks will fail if they rely on secrets. Use a pattern where analysis/test runs without secrets, and deployment requires secrets but runs only on main.

Sign in to like, dislike, or report.

Setting up GitHub Actions for Flutter from scratch — ANN Tech