Flutter'da CI/CD: GitHub Actions ile Otomatik Build ve Dağıtım
Her push veya pull request'te testlerin çalışması, APK/IPA oluşturulması ve dağıtımın otomatik yapılması; hataları erken yakalar ve deployment sürecini güvenilir kılar. Bu yazıda GitHub Actions ile Flutter için eksiksiz bir CI/CD pipeline kuracağız.
Temel Workflow: Test ve Analiz
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
name: Test & Analyze
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Flutter Setup
uses: subosito/flutter-action@v2
with:
flutter-version: '3.24.x'
channel: stable
cache: true
- name: Cache pub packages
uses: actions/cache@v3
with:
path: ~/.pub-cache
key: ${{ runner.os }}-pub-${{ hashFiles('**/pubspec.lock') }}
restore-keys: ${{ runner.os }}-pub-
- name: Install dependencies
run: flutter pub get
- name: Generate code
run: dart run build_runner build --delete-conflicting-outputs
- name: Analyze
run: flutter analyze --no-fatal-infos
- name: Format check
run: dart format --set-exit-if-changed lib/ test/
- name: Run tests
run: flutter test --coverage --reporter=github
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
file: coverage/lcov.info
Android Build ve İmzalama
# .github/workflows/android.yml
name: Android Build
on:
push:
branches: [main]
workflow_dispatch:
jobs:
build-android:
name: Build Android
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '17'
- uses: subosito/flutter-action@v2
with:
flutter-version: '3.24.x'
channel: stable
cache: true
- name: Flutter pub get & codegen
run: |
flutter pub get
dart run build_runner build --delete-conflicting-outputs
# Keystore dosyasını Secret'tan oluştur
- name: Decode Keystore
run: |
echo "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" | base64 --decode > android/app/release.keystore
# key.properties dosyası oluştur
- name: Create key.properties
run: |
cat > android/key.properties <<EOF
storePassword=${{ secrets.ANDROID_STORE_PASSWORD }}
keyPassword=${{ secrets.ANDROID_KEY_PASSWORD }}
keyAlias=${{ secrets.ANDROID_KEY_ALIAS }}
storeFile=release.keystore
EOF
# .env dosyası oluştur (API keys vb.)
- name: Create .env
run: |
echo "API_URL=${{ secrets.API_URL }}" > .env
echo "GOOGLE_MAPS_KEY=${{ secrets.GOOGLE_MAPS_KEY }}" >> .env
- name: Build APK (debug)
run: flutter build apk --debug --flavor dev -t lib/main_dev.dart
- name: Build App Bundle (release)
run: |
flutter build appbundle \
--release \
--flavor prod \
-t lib/main_prod.dart \
--dart-define=FLAVOR=prod
- name: Upload APK Artifact
uses: actions/upload-artifact@v4
with:
name: app-debug-apk
path: build/app/outputs/flutter-apk/app-debug.apk
# Firebase App Distribution'a yükle
- name: Upload to Firebase App Distribution
uses: wzieba/Firebase-Distribution-Github-Action@v1
with:
appId: ${{ secrets.FIREBASE_APP_ID_ANDROID }}
serviceCredentialsFileContent: ${{ secrets.FIREBASE_SERVICE_ACCOUNT }}
groups: qa-team
file: build/app/outputs/flutter-apk/app-debug.apk
releaseNotes: |
Build: ${{ github.run_number }}
Commit: ${{ github.sha }}
Branch: ${{ github.ref_name }}
iOS Build ve İmzalama
# .github/workflows/ios.yml (kısmen)
build-ios:
name: Build iOS
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
flutter-version: '3.24.x'
channel: stable
# Sertifikaları kur
- name: Install Apple Certificate
uses: apple-actions/import-codesign-certs@v2
with:
p12-file-base64: ${{ secrets.IOS_P12_BASE64 }}
p12-password: ${{ secrets.IOS_P12_PASSWORD }}
# Provisioning profile kur
- name: Install Provisioning Profile
run: |
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
echo "${{ secrets.IOS_PROVISIONING_PROFILE_BASE64 }}" | base64 --decode \
> ~/Library/MobileDevice/Provisioning\ Profiles/profile.mobileprovision
- name: Flutter build iOS
run: |
flutter pub get
dart run build_runner build --delete-conflicting-outputs
flutter build ipa \
--release \
--export-options-plist ios/ExportOptions.plist
# TestFlight'a yükle
- name: Upload to TestFlight
uses: apple-actions/upload-testflight-build@v1
with:
app-path: build/ios/ipa/*.ipa
issuer-id: ${{ secrets.APPSTORE_ISSUER_ID }}
api-key-id: ${{ secrets.APPSTORE_KEY_ID }}
api-private-key: ${{ secrets.APPSTORE_PRIVATE_KEY }}
Environment ve Flavor Yönetimi
// lib/core/env.dart
enum AppFlavor { dev, staging, prod }
class Env {
static late AppFlavor flavor;
static late String apiUrl;
static late String googleMapsKey;
static void initialize() {
const flavorStr = String.fromEnvironment('FLAVOR', defaultValue: 'dev');
flavor = AppFlavor.values.byName(flavorStr);
switch (flavor) {
case AppFlavor.dev:
apiUrl = 'https://dev.api.example.com';
googleMapsKey = const String.fromEnvironment('GOOGLE_MAPS_KEY');
case AppFlavor.staging:
apiUrl = 'https://staging.api.example.com';
googleMapsKey = const String.fromEnvironment('GOOGLE_MAPS_KEY');
case AppFlavor.prod:
apiUrl = 'https://api.example.com';
googleMapsKey = const String.fromEnvironment('GOOGLE_MAPS_KEY');
}
}
}
// main_dev.dart
void main() {
Env.initialize();
runApp(const ProviderScope(child: MyApp()));
}
// Çalıştırma:
// flutter run -t lib/main_dev.dart --dart-define=FLAVOR=dev
// flutter build apk -t lib/main_prod.dart --dart-define=FLAVOR=prod
Semantic Versioning ve Git Tag ile Sürüm Yönetimi
# pubspec.yaml versiyonunu otomatik güncelle
# .github/workflows/release.yml
- name: Bump version
run: |
VERSION=$(cat VERSION)
BUILD=${{ github.run_number }}
sed -i "s/^version:.*/version: $VERSION+$BUILD/" pubspec.yaml
- name: Commit version bump
run: |
git config user.email "ci@example.com"
git config user.name "CI Bot"
git add pubspec.yaml
git commit -m "chore: bump version to $VERSION+$BUILD [skip ci]"
git push
GitHub Actions, Flutter CI/CD için ücretsiz ve yeterince güçlüdür. Android için keystore'u, iOS için p12 sertifikasını ve provisioning profile'ı GitHub Secrets'a ekleyin. Flavor sistemi ile dev/staging/prod ortamlarını ayırın. Firebase App Distribution, QA ekibine hızlı dağıtım için mükemmeldir.