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_BASE64KEYSTORE_PASSWORDKEY_ALIASKEY_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.