Publishing mobile applications (Flutter) to App Store and Google Play Store.
| Platform | Internal Testing | Production |
|---|---|---|
| iOS | TestFlight | App Store |
| Android | Internal Track, Firebase | Play Store |
# Initialize project
taskfile run init
# Development
taskfile run dev # Start dev mode
taskfile run test # Run tests
taskfile run analyze # Static analysis
# Build
taskfile --platform ios run build-ios # iOS IPA
taskfile --platform android run build-android-aab # Android AAB
# Staging (Internal Testing)
taskfile --env staging run release-staging
# Production (App Store + Play Store)
taskfile --env prod run release-productiontaskfile run initCreates .env.local with placeholders:
- Apple Developer credentials
- Google Play Service Account
- Firebase token (optional)
brew install fastlanetaskfile run setup-ios-certOr manually:
cd ios && fastlane match development
cd ios && fastlane match appstoreAPPLE_ID=your@email.com
APPLE_TEAM_ID=ABCDEF1234
FASTLANE_PASSWORD=your-passwordtaskfile run setup-android-keystore- Google Play Console → Setup → API Access
- Create Service Account → Download JSON key
- Save as
android/play-store-key.json
PLAYSTORE_JSON=android/play-store-key.jsonnpm install -g firebase-tools
firebase login:ciSet in .env:
FIREBASE_TOKEN=your-token
FIREBASE_APP_ID=1:123456789:android:abcdef
FIREBASE_DIST=true┌─────────────┐ ┌─────────────────────┐ ┌───────────────────┐
│ Development │───▶│ Internal Testing │───▶│ Production │
│ │ │ │ │ │
│ • Local dev │ │ • TestFlight (iOS) │ │ • App Store │
│ • Tests │ │ • Internal Track │ │ • Play Store │
│ • Build │ │ • Firebase Dist │ │ • Public release │
└─────────────┘ └─────────────────────┘ └───────────────────┘
| Task | Description |
|---|---|
dev |
Start Flutter dev mode |
test |
Run unit/widget tests |
analyze |
Static analysis |
install |
Install dependencies |
| Task | Platform | Output |
|---|---|---|
build-ios |
iOS | .ipa for App Store |
build-android-apk |
Android | .apk for Firebase |
build-android-aab |
Android | .aab for Play Store |
build-all |
Both | All artifacts |
| Task | Description |
|---|---|
setup-ios-cert |
Setup iOS certificates (Fastlane match) |
setup-android-keystore |
Create Android keystore |
| Task | Platform | Channel |
|---|---|---|
deploy-testflight |
iOS | TestFlight |
deploy-play-internal |
Android | Play Store Internal |
deploy-firebase |
Android | Firebase App Distribution |
release-staging |
Both | All staging channels |
| Task | Platform | Store |
|---|---|---|
release-appstore |
iOS | App Store |
release-playstore |
Android | Play Store |
release-production |
Both | Both stores |
| Environment | Use Case | Channels |
|---|---|---|
local |
Development | Local device/simulator |
staging |
QA, Beta testers | TestFlight, Internal Track, Firebase |
prod |
Public release | App Store, Play Store |
name: Mobile CI/CD
on:
push:
branches: [main]
workflow_dispatch:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
- run: flutter pub get
- run: taskfile run test
- run: taskfile run analyze
deploy-staging:
needs: test
runs-on: macos-latest # macOS for both iOS and Android
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
- run: flutter pub get
- run: taskfile --env staging run release-staging
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
FASTLANE_PASSWORD: ${{ secrets.FASTLANE_PASSWORD }}
PLAYSTORE_JSON: ${{ secrets.PLAYSTORE_JSON }}
deploy-production:
needs: deploy-staging
runs-on: macos-latest
if: startsWith(github.ref, 'refs/tags/v')
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
- run: flutter pub get
- run: taskfile --env prod run release-production
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
FASTLANE_PASSWORD: ${{ secrets.FASTLANE_PASSWORD }}
PLAYSTORE_JSON: ${{ secrets.PLAYSTORE_JSON }}# ios/fastlane/Fastfile
default_platform(:ios)
platform :ios do
desc "Push to TestFlight"
lane :beta do
build_app(workspace: "Runner.xcworkspace", scheme: "Runner")
upload_to_testflight
end
desc "Release to App Store"
lane :release do
build_app(workspace: "Runner.xcworkspace", scheme: "Runner")
upload_to_app_store
end
end# android/fastlane/Fastfile
default_platform(:android)
platform :android do
desc "Internal testing"
lane :internal do
gradle(task: "bundleRelease")
upload_to_play_store(track: 'internal')
end
desc "Deploy to Play Store"
lane :deploy do
gradle(task: "bundleRelease")
upload_to_play_store
end
endFlutter uses version: 1.0.0+1 in pubspec.yaml:
1.0.0= version+1= build number
Bump with:
# Manual bump
taskfile run bump-build
# Or update pubspec.yaml directly
version: 1.1.0+2# Capture screenshots on all devices
taskfile run screenshotsUses Fastlane's snapshot (iOS) and screengrab (Android).
# Upload descriptions, screenshots without app
taskfile run metadata# Install on device via TestFlight
# 1. Build and upload
TASKFILE_PLATFORM=ios taskfile --env staging run deploy-testflight
# 2. Open TestFlight app on device
# 3. Install and test# Install APK directly
flutter build apk --release
adb install build/app/outputs/flutter-apk/app-release.apk
# Or use Firebase Distribution
TASKFILE_PLATFORM=android taskfile --env staging run deploy-firebase# Reset certificates
cd ios && fastlane match nuke development
cd ios && fastlane match nuke appstore
taskfile run setup-ios-cert# Verify keystore
keytool -list -v -keystore android/app/keystore.jks
# Check signing config
cd android && ./gradlew signingReportCheck:
- Privacy policy URL set
- Content rating completed
- App category selected
- Screenshots uploaded