Flutter CI/CD Setup
Automating your Flutter app builds and deployments
🚀 What is CI/CD?
CI/CD (Continuous Integration/Continuous Deployment) automates building, testing, and deploying your Flutter apps. It ensures code quality and speeds up releases by automatically running tests and builds whenever you push code changes.
# Basic CI/CD workflow example
name: Flutter CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: subosito/flutter-action@v2
- run: flutter pub get
- run: flutter test
- run: flutter build apk
Key CI/CD Concepts
Continuous Integration
Automatically merge and test code changes. Every commit triggers automated builds and tests to catch bugs early before they reach production.
- run: flutter test
- run: flutter analyze
Continuous Deployment
Automatically deploy apps to stores or servers. Once tests pass, your app is built and deployed to Google Play, App Store, or web hosting automatically.
- run: flutter build appbundle
- name: Deploy to Play Store
Automated Testing
Run tests on every code change. Unit tests, widget tests, and integration tests run automatically to ensure your app works correctly before deployment.
- run: flutter test
- run: flutter drive --target=test_driver/app.dart
Build Automation
Generate app builds automatically. Create APKs, IPAs, or web builds without manual intervention, saving time and reducing human error in the build process.
- run: flutter build apk --release
- run: flutter build ios --release
🔹 GitHub Actions Setup
Create a CI/CD pipeline using GitHub Actions:
# .github/workflows/flutter.yml
name: Flutter CI/CD
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: '3.16.0'
- name: Install dependencies
run: flutter pub get
- name: Run tests
run: flutter test
- name: Build APK
run: flutter build apk --release
- name: Upload APK
uses: actions/upload-artifact@v3
with:
name: app-release
path: build/app/outputs/flutter-apk/app-release.apk
What this does:
✓ Triggers on every push to main branch
✓ Sets up Flutter environment
✓ Installs dependencies
✓ Runs all tests
✓ Builds release APK
✓ Saves APK as downloadable artifact
🔹 Codemagic Configuration
Codemagic is a popular CI/CD tool specifically for Flutter:
# codemagic.yaml
workflows:
flutter-workflow:
name: Flutter Build
max_build_duration: 60
environment:
flutter: stable
xcode: latest
scripts:
- name: Get dependencies
script: flutter pub get
- name: Run tests
script: flutter test
- name: Build Android
script: flutter build appbundle --release
- name: Build iOS
script: flutter build ipa --release
artifacts:
- build/**/outputs/**/*.aab
- build/**/outputs/**/*.apk
- build/ios/ipa/*.ipa
publishing:
google_play:
credentials: $GOOGLE_PLAY_CREDENTIALS
track: internal
Features:
✓ Builds for both Android and iOS
✓ Automatically publishes to Google Play
✓ Saves all build artifacts
✓ Uses latest stable Flutter version
🔹 GitLab CI Setup
Configure CI/CD with GitLab:
# .gitlab-ci.yml
image: cirrusci/flutter:stable
stages:
- test
- build
- deploy
test:
stage: test
script:
- flutter pub get
- flutter analyze
- flutter test
only:
- merge_requests
- main
build_android:
stage: build
script:
- flutter pub get
- flutter build apk --release
artifacts:
paths:
- build/app/outputs/flutter-apk/app-release.apk
only:
- main
deploy:
stage: deploy
script:
- echo "Deploying to production"
# Add your deployment script here
only:
- main
Pipeline stages:
1. Test: Runs on every merge request
2. Build: Creates release APK
3. Deploy: Pushes to production
🔹 Basic Flutter Test Setup
Create tests that CI/CD will run automatically:
// test/widget_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:my_app/main.dart';
void main() {
testWidgets('Counter increments test', (WidgetTester tester) async {
// Build our app and trigger a frame
await tester.pumpWidget(const MyApp());
// Verify counter starts at 0
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);
// Tap the '+' icon and trigger a frame
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
// Verify counter incremented
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
});
}
Run tests:
flutter test
- Run all tests
flutter test --coverage
- With coverage report