Flutter Build & Deployment Patterns
Flavors (Build Variants)
// lib/main_dev.dart
void main() => runApp(const App(flavor: Flavor.dev));
// lib/main_prod.dart
void main() => runApp(const App(flavor: Flavor.prod));
// lib/flavor.dart
enum Flavor { dev, staging, prod }
class FlavorConfig {
static late final Flavor current;
static bool get isDev => current == Flavor.dev;
static String get apiBaseUrl => switch (current) {
Flavor.dev => 'https://dev-api.example.com',
Flavor.staging => 'https://staging-api.example.com',
Flavor.prod => 'https://api.example.com',
};
}
# Run with flavor
flutter run --flavor dev -t lib/main_dev.dart
flutter run --flavor prod -t lib/main_prod.dart
# Build
flutter build apk --flavor prod -t lib/main_prod.dart --release
flutter build ipa --flavor prod -t lib/main_prod.dart --release
Android Signing
// android/app/build.gradle
android {
signingConfigs {
release {
keyAlias System.getenv("KEY_ALIAS")
keyPassword System.getenv("KEY_PASSWORD")
storeFile file(System.getenv("KEYSTORE_PATH") ?: "keystore.jks")
storePassword System.getenv("STORE_PASSWORD")
}
}
buildTypes {
release { signingConfig signingConfigs.release }
}
}
iOS Signing (fastlane match)
# Matchfile
git_url("https://github.com/org/certificates")
storage_mode("git")
type("appstore")
app_identifier(["com.example.app"])
# Fastfile
lane :beta do
match(type: "appstore")
build_ios_app(
workspace: "Runner.xcworkspace",
configuration: "Release-prod",
export_method: "app-store"
)
upload_to_testflight
end
GitHub Actions CI/CD
# .github/workflows/flutter.yml
name: Flutter CI
on:
push:
branches: [main, develop]
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
flutter-version: '3.24.0'
cache: true
- run: flutter pub get
- run: flutter analyze
- run: flutter test --coverage
- uses: codecov/codecov-action@v4
with:
file: coverage/lcov.info
build-android:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
flutter-version: '3.24.0'
cache: true
- name: Decode keystore
run: echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > android/app/keystore.jks
- run: flutter build apk --flavor prod -t lib/main_prod.dart --release
env:
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
STORE_PASSWORD: ${{ secrets.STORE_PASSWORD }}
KEYSTORE_PATH: keystore.jks
- uses: actions/upload-artifact@v4
with:
name: release-apk
path: build/app/outputs/apk/prod/release/*.apk
build-ios:
needs: test
runs-on: macos-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
flutter-version: '3.24.0'
cache: true
- run: flutter build ipa --flavor prod -t lib/main_prod.dart --release --no-codesign
- uses: actions/upload-artifact@v4
with:
name: release-ipa
path: build/ios/ipa/*.ipa
Shorebird (OTA Code Push)
# Install
curl --proto '=https' --tlsv1.2 https://raw.githubusercontent.com/shorebirdtech/install/main/install.sh -sSf | bash
# Initialize
shorebird init
# Release (uploads to Shorebird CDN)
shorebird release android --flavor prod -t lib/main_prod.dart
shorebird release ios --flavor prod -t lib/main_prod.dart
# Patch (OTA update — no store review)
shorebird patch android --flavor prod
shorebird patch ios --flavor prod
dart_defines for Environment Variables
# Pass at build time (safer than .env files)
flutter run \
--dart-define=API_URL=https://api.example.com \
--dart-define=SENTRY_DSN=https://xxx@sentry.io/123
# In Dart code
const apiUrl = String.fromEnvironment('API_URL', defaultValue: 'http://localhost:8080');
const sentryDsn = String.fromEnvironment('SENTRY_DSN');
Obfuscation & Minification
# Android
flutter build apk --release --obfuscate --split-debug-info=build/debug-info/android
# iOS
flutter build ipa --release --obfuscate --split-debug-info=build/debug-info/ios
# Upload debug symbols to Sentry
sentry-cli upload-dif build/debug-info/
Version Management
# pubspec.yaml
version: 2.4.1+47 # semver+buildNumber
# Auto-increment in CI
# Use flutter_version or cider package, or sed:
# sed -i "s/version:.*/version: $VERSION+$BUILD_NUMBER/" pubspec.yaml
Build Checklist