
Modern app development requires automated testing and deployment pipelines. Manual testing and deployment are time-consuming, error-prone, and don't scale with growing teams.
This guide will show you how to set up robust CI/CD pipelines for Flutter apps using GitHub Actions. You'll learn to automate testing, code quality checks, builds, and deployments to both Google Play Store and Apple App Store.
GITHUB ACTIONS FOUNDATION
GitHub Actions provides free CI/CD for public repositories and generous limits for private ones. It integrates seamlessly with your Flutter development workflow.
# .github/workflows/flutter_ci.yml name: Flutter CI on: push: branches: [ main, develop ] pull_request: branches: [ main ] jobs: test: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Set up Flutter uses: subosito/flutter-action@v2 with: flutter-version: '3.24.0' channel: 'stable' cache: true - name: Install dependencies run: flutter pub get - name: Verify formatting run: dart format --output=none --set-exit-if-changed . - name: Analyze project source run: flutter analyze --fatal-infos - name: Run tests run: flutter test --coverage - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 with: files: ./coverage/lcov.info fail_ci_if_error: true build_android: needs: test runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Set up Flutter uses: subosito/flutter-action@v2 with: flutter-version: '3.24.0' channel: 'stable' cache: true - name: Install dependencies run: flutter pub get - name: Build Android APK run: flutter build apk --release - name: Build Android App Bundle run: flutter build appbundle --release - name: Upload APK artifact uses: actions/upload-artifact@v3 with: name: android-apk path: build/app/outputs/flutter-apk/app-release.apk - name: Upload AAB artifact uses: actions/upload-artifact@v3 with: name: android-aab path: build/app/outputs/bundle/release/app-release.aab build_ios: needs: test runs-on: macos-latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Set up Flutter uses: subosito/flutter-action@v2 with: flutter-version: '3.24.0' channel: 'stable' cache: true - name: Install dependencies run: flutter pub get - name: Build iOS (no signing) run: flutter build ios --release --no-codesign - name: Upload iOS artifact uses: actions/upload-artifact@v3 with: name: ios-build path: build/ios/iphoneos/Runner.app
✅ WORKFLOW BENEFITS
- • Runs on every push and PR
- • Parallel Android and iOS builds
- • Automated code quality checks
- • Coverage reporting
🎯 KEY FEATURES
- • Flutter version caching
- • Dependency caching
- • Artifact uploads
- • Multi-platform support
COMPREHENSIVE TESTING PIPELINE
A robust testing pipeline includes unit tests, widget tests, integration tests, and code quality checks. This ensures your app is reliable before deployment.
# .github/workflows/comprehensive_testing.yml name: Comprehensive Testing on: push: branches: [ main, develop ] pull_request: branches: [ main ] jobs: quality_checks: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Set up Flutter uses: subosito/flutter-action@v2 with: flutter-version: '3.24.0' channel: 'stable' cache: true - name: Install dependencies run: flutter pub get # Code formatting check - name: Check code formatting run: dart format --output=none --set-exit-if-changed . # Static analysis - name: Run static analysis run: flutter analyze --fatal-infos --fatal-warnings # Import sorting check - name: Check import sorting run: | flutter pub global activate import_sorter import_sorter --check-only # Unused dependencies check - name: Check for unused dependencies run: | flutter pub global activate dependency_validator dependency_validator # Security audit - name: Security audit run: | flutter pub deps --json > deps.json # Add security scanning tool here unit_tests: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Set up Flutter uses: subosito/flutter-action@v2 with: flutter-version: '3.24.0' channel: 'stable' cache: true - name: Install dependencies run: flutter pub get - name: Run unit tests run: | flutter test \ --coverage \ --test-randomize-ordering-seed random \ --reporter expanded - name: Generate coverage report run: | sudo apt-get update sudo apt-get install lcov genhtml coverage/lcov.info -o coverage/html - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 with: files: ./coverage/lcov.info fail_ci_if_error: true verbose: true widget_tests: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Set up Flutter uses: subosito/flutter-action@v2 with: flutter-version: '3.24.0' channel: 'stable' cache: true - name: Install dependencies run: flutter pub get - name: Run widget tests run: flutter test test/widget_test/ integration_tests: runs-on: macos-latest strategy: matrix: device: - "iPhone 14 Pro Simulator (16.0)" - "iPad Pro (11-inch) (4th generation) Simulator (16.0)" steps: - name: Checkout repository uses: actions/checkout@v4 - name: Set up Flutter uses: subosito/flutter-action@v2 with: flutter-version: '3.24.0' channel: 'stable' cache: true - name: Install dependencies run: flutter pub get - name: Start iOS Simulator run: | xcrun simctl list devices xcrun simctl boot "${{ matrix.device }}" || true - name: Run integration tests run: | flutter test integration_test/ \ --device-id="${{ matrix.device }}" - name: Upload test results uses: actions/upload-artifact@v3 if: failure() with: name: integration-test-results path: test_results/
🧪 Testing Strategy:
- • Unit tests for business logic (70% of tests)
- • Widget tests for UI components (20% of tests)
- • Integration tests for user flows (10% of tests)
- • Code coverage threshold enforcement
AUTOMATED DEPLOYMENT PIPELINE
Deploy your Flutter app to Google Play Store and Apple App Store automatically when tests pass. This includes app signing, version management, and release notes.
# .github/workflows/deploy.yml name: Deploy to App Stores on: push: tags: - 'v*' # Deploy when version tags are pushed jobs: deploy_android: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Set up Flutter uses: subosito/flutter-action@v2 with: flutter-version: '3.24.0' channel: 'stable' cache: true - name: Install dependencies run: flutter pub get # Setup Android signing - name: Setup Android signing run: | echo "$ANDROID_KEYSTORE_BASE64" | base64 -d > android/app/keystore.jks echo "storeFile=keystore.jks" >> android/key.properties echo "keyAlias=$ANDROID_KEY_ALIAS" >> android/key.properties echo "storePassword=$ANDROID_STORE_PASSWORD" >> android/key.properties echo "keyPassword=$ANDROID_KEY_PASSWORD" >> android/key.properties env: ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} ANDROID_STORE_PASSWORD: ${{ secrets.ANDROID_STORE_PASSWORD }} ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} - name: Build signed App Bundle run: flutter build appbundle --release - name: Deploy to Google Play Console uses: r0adkll/upload-google-play@v1 with: serviceAccountJsonPlainText: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON }} packageName: com.onon.yourapp releaseFiles: build/app/outputs/bundle/release/app-release.aab track: production status: completed inAppUpdatePriority: 2 userFraction: 0.33 whatsNewDirectory: distribution/whatsnew mappingFile: build/app/outputs/mapping/release/mapping.txt deploy_ios: runs-on: macos-latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Set up Flutter uses: subosito/flutter-action@v2 with: flutter-version: '3.24.0' channel: 'stable' cache: true - name: Install dependencies run: flutter pub get # Setup iOS certificates and provisioning profiles - name: Install Apple Certificate uses: apple-actions/import-codesign-certs@v1 with: p12-file-base64: ${{ secrets.IOS_CERTIFICATE_BASE64 }} p12-password: ${{ secrets.IOS_CERTIFICATE_PASSWORD }} - name: Install Provisioning Profile uses: apple-actions/download-provisioning-profiles@v1 with: bundle-id: com.onon.yourapp issuer-id: ${{ secrets.APPSTORE_ISSUER_ID }} api-key-id: ${{ secrets.APPSTORE_KEY_ID }} api-private-key: ${{ secrets.APPSTORE_PRIVATE_KEY }} - name: Build iOS app run: | flutter build ios --release --no-codesign cd ios xcodebuild -workspace Runner.xcworkspace \ -scheme Runner \ -configuration Release \ -destination generic/platform=iOS \ -archivePath $PWD/build/Runner.xcarchive \ archive xcodebuild -exportArchive \ -archivePath $PWD/build/Runner.xcarchive \ -exportOptionsPlist ExportOptions.plist \ -exportPath $PWD/build - name: Deploy to TestFlight uses: apple-actions/upload-testflight-build@v1 with: app-path: ios/build/Runner.ipa issuer-id: ${{ secrets.APPSTORE_ISSUER_ID }} api-key-id: ${{ secrets.APPSTORE_KEY_ID }} api-private-key: ${{ secrets.APPSTORE_PRIVATE_KEY }} create_release: needs: [deploy_android, deploy_ios] runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Create GitHub Release uses: actions/create-release@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: tag_name: ${{ github.ref }} release_name: Release ${{ github.ref }} body: | 🚀 New release deployed to app stores! ## What's New - Check CHANGELOG.md for detailed changes ## Deployment Status - ✅ Android: Google Play Store - ✅ iOS: TestFlight & App Store draft: false prerelease: false
🚀 Deployment Features:
- • Automated app signing for both platforms
- • Staged rollout for risk mitigation
- • Automatic release notes from changelogs
- • Version management and tagging
ENVIRONMENT & SECRET MANAGEMENT
Proper secret management and environment configuration ensure secure deployments while supporting different stages like development, staging, and production.
# .github/workflows/multi_environment.yml name: Multi-Environment Deployment on: push: branches: - develop # Deploy to staging - main # Deploy to production pull_request: branches: - main # Test on PR jobs: determine_environment: runs-on: ubuntu-latest outputs: environment: ${{ steps.env.outputs.environment }} api_url: ${{ steps.env.outputs.api_url }} steps: - name: Determine environment id: env run: | if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then echo "environment=production" >> $GITHUB_OUTPUT echo "api_url=https://api.yourapp.com" >> $GITHUB_OUTPUT elif [[ "${{ github.ref }}" == "refs/heads/develop" ]]; then echo "environment=staging" >> $GITHUB_OUTPUT echo "api_url=https://staging-api.yourapp.com" >> $GITHUB_OUTPUT else echo "environment=test" >> $GITHUB_OUTPUT echo "api_url=https://test-api.yourapp.com" >> $GITHUB_OUTPUT fi build_and_test: needs: determine_environment runs-on: ubuntu-latest environment: ${{ needs.determine_environment.outputs.environment }} steps: - name: Checkout repository uses: actions/checkout@v4 - name: Set up Flutter uses: subosito/flutter-action@v2 with: flutter-version: '3.24.0' channel: 'stable' cache: true - name: Create environment config run: | cat > lib/config/environment.dart << EOF class Environment { static const String apiUrl = '${{ needs.determine_environment.outputs.api_url }}'; static const String environment = '${{ needs.determine_environment.outputs.environment }}'; static const String apiKey = '${{ secrets.API_KEY }}'; static const bool isProduction = ${{ needs.determine_environment.outputs.environment == 'production' }}; } EOF - name: Install dependencies run: flutter pub get - name: Run tests run: flutter test - name: Build for environment run: | if [[ "${{ needs.determine_environment.outputs.environment }}" == "production" ]]; then flutter build apk --release --flavor production -t lib/main_production.dart elif [[ "${{ needs.determine_environment.outputs.environment }}" == "staging" ]]; then flutter build apk --release --flavor staging -t lib/main_staging.dart else flutter build apk --debug --flavor development -t lib/main_development.dart fi # GitHub Repository Secrets Setup: # ANDROID_KEYSTORE_BASE64 - Base64 encoded Android keystore # ANDROID_KEY_ALIAS - Android key alias # ANDROID_STORE_PASSWORD - Android keystore password # ANDROID_KEY_PASSWORD - Android key password # IOS_CERTIFICATE_BASE64 - Base64 encoded iOS certificate # IOS_CERTIFICATE_PASSWORD - iOS certificate password # APPSTORE_ISSUER_ID - App Store Connect issuer ID # APPSTORE_KEY_ID - App Store Connect key ID # APPSTORE_PRIVATE_KEY - App Store Connect private key # GOOGLE_PLAY_SERVICE_ACCOUNT_JSON - Google Play Console service account # API_KEY - Your app's API key # FIREBASE_CONFIG_ANDROID - Firebase config for Android # FIREBASE_CONFIG_IOS - Firebase config for iOS # Environment-specific secrets: # For each environment (production, staging, test), create: # - API_URL # - DATABASE_URL # - ANALYTICS_KEY # - Feature flags configuration
🔐 Security Best Practices:
- • Store all sensitive data in GitHub Secrets
- • Use environment-specific configurations
- • Rotate secrets regularly
- • Limit secret access to necessary workflows only
CI/CD BEST PRACTICES
Fast Feedback Loop: Keep build times under 10 minutes. Use caching, parallel jobs, and incremental builds to speed up the pipeline.
Fail Fast: Run static analysis and unit tests first. Don't waste time on expensive integration tests if basic quality checks fail.
Staged Deployments: Deploy to staging first, then production. Use feature flags and gradual rollouts to minimize risk.
Monitor Everything: Track build success rates, test coverage, deployment frequency, and lead time for changes.
Rollback Strategy: Always have a plan to quickly rollback deployments. Keep previous versions available and test rollback procedures.