Flutter: Automação CI/CD
Releases manuais são um terreno fértil para erros humanos — automação torna cada passo repetível, rastreável e confiável.
📋 Pré-requisitos: Você deve ter dominado o seguinte primeiro
1. O Que Você Vai Aprender
- Workflows do GitHub Actions: flutter test / flutter build builds multi-matriz
- Automação de assinatura de código: keystore Android / certificados iOS e gerenciamento de Provisioning Profile
- Integração Fastlane: screenshots automatizados / TestFlight automatizado / uploads para Play Console
- Comparação de soluções de CI na nuvem: Codemagic / Bitrise
- ShopApp: build automatizado multiplataforma com GitHub Actions + notificação Slack + release com auto-tag
2. Uma História Real de um Desastre de Release Manual
(1) A Dor: 4 Horas de Releases Manuais + 3 Ciclos de Retrabalho
Toda vez que Bob lança uma nova versão do ShopApp, ele precisa: executar flutter build manualmente → assinar → fazer upload para o Play Console → tirar screenshots → preencher notas de release → enviar para revisão. O processo iOS é ainda mais complexo: Archive → exportar IPA → fazer upload para o App Store Connect → aguardar revisão. O processo inteiro leva 4 horas, com pelo menos 2 ciclos de retrabalho (esqueceu de atualizar versão / config de assinatura errada / screenshots não conformes). Todo dia de release, Bob trabalha até tarde da noite.
(2) Solução com Pipeline CI/CD
Automação com GitHub Actions: push de tag → teste automático → build automático → assinatura automática → upload automático → notificação Slack. Zero intervenção manual do início ao fim.
# Push tag v1.2.3 → pipeline de release automático
on: push: tags: ['v*']
# → flutter test → flutter build → sign → upload → notify
(3) O Resultado: Releases de 1 Hora + Zero Retrabalho
Depois de configurar o CI/CD, o tempo de release do Bob caiu de 4 horas para 1 hora (a maior parte esperando revisão), e a taxa de retrabalho caiu de 60% para 0%.
3. Visão Geral do Pipeline CI/CD
graph LR
subgraph GitHub Actions Pipeline
P[Push / PR] --> LINT[Lint & Analyze]
LINT --> TEST[flutter test]
TEST --> BUILD[Build APK/IPA/Web]
BUILD --> SIGN[Code Signing]
SIGN --> DIST[Distribute]
DIST --> |Slack| NOTIFY[Team Notification]
end
> Saída: Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.
(1) Comparação de Soluções CI/CD
| Solução | Preço | Suporte de Plataformas | Configuração | Melhor Para |
|---|---|---|---|---|
| GitHub Actions | Grátis (2000 min/mês) | Todas as plataformas | YAML | Primeira escolha |
| Codemagic | Grátis (500 min/mês) | Todas as plataformas | YAML | Melhores builds macOS |
| Bitrise | Grátis (200 min/mês) | Todas as plataformas | UI + YAML | Específico para iOS |
| Fastlane | Grátis (local) | iOS/Android | Ruby | Automação local |
| Cirrus CI | Grátis (código aberto) | Todas as plataformas | YAML | Projetos comunitários |
4. Workflows do GitHub Actions
▶ Exemplo
> Saída: Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.
: Workflow de verificação de PR
# .github/workflows/pr-check.yml
name: PR Check
on:
pull_request:
branches: [main]
jobs:
analyze-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: '3.x'
channel: 'stable'
- name: Install dependencies
run: flutter pub get
- name: Analyze code
run: flutter analyze --no-fatal-infos
- name: Run unit and widget tests
run: flutter test --coverage
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
files: coverage/lcov.info
▶ Exemplo
> Saída: Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.
: Workflow de build multi-matriz
# .github/workflows/build.yml
name: Build All Platforms
on:
push:
tags: ['v*']
jobs:
build-android:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with: { flutter-version: '3.x' }
- run: flutter pub get
- run: flutter test
- name: Build APK
run: flutter build apk --release
- name: Build AAB
run: flutter build appbundle --release
env:
KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }}
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
- name: Upload to Play Console
uses: r0adkll/upload-google-play@v1
with:
serviceAccountJsonPlainText: ${{ secrets.PLAY_SERVICE_ACCOUNT }}
packageName: com.shopapp.app
releaseFiles: build/app/outputs/bundle/release/app-release.aab
track: production
build-ios:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with: { flutter-version: '3.x' }
- run: flutter pub get
- name: Install CocoaPods
run: cd ios && pod install
- name: Build IPA
run: flutter build ipa --release --export-options-plist=ios/ExportOptions.plist
- name: Upload to App Store Connect
uses: apple/upload-testflight@v1
with:
app-path: build/ios/ipa/shopapp.ipa
issuer-id: ${{ secrets.APPSTORE_ISSUER_ID }}
api-key-id: ${{ secrets.APPSTORE_KEY_ID }}
api-private-key: ${{ secrets.APPSTORE_PRIVATE_KEY }}
build-web:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with: { flutter-version: '3.x' }
- run: flutter pub get
- run: flutter build web --wasm
- name: Deploy to Firebase Hosting
uses: FirebaseExtended/action-hosting-deploy@v0
with:
repoToken: ${{ secrets.GITHUB_TOKEN }}
firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT }}
channelId: live
notify:
needs: [build-android, build-ios, build-web]
runs-on: ubuntu-latest
steps:
- name: Slack Notification
uses: slackapi/slack-github-action@v1
with:
payload: |
{"text": "ShopApp ${{ github.ref_name }} lançado com sucesso!"}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}
5. Automação de Assinatura de Código
▶ Exemplo
> Saída: Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.
: Configuração de assinatura Android
# GitHub Secrets necessários:
# KEYSTORE_BASE64: arquivo .jks codificado em base64
# KEY_PASSWORD: senha do keystore
# KEY_ALIAS: alias da chave
- name: Decode keystore
run: echo "$KEYSTORE_BASE64" | base64 --decode > android/app/keystore.jks
env:
KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }}
// android/app/build.gradle
android {
signingConfigs {
release {
storeFile file('keystore.jks')
storePassword System.getenv("KEY_PASSWORD")
keyAlias System.getenv("KEY_ALIAS")
keyPassword System.getenv("KEY_PASSWORD")
}
}
buildTypes {
release {
signingConfig signingConfigs.release
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
> Saída: Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.
6. Integração Fastlane
▶ Exemplo
> Saída: Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.
: Configuração do Fastlane
# ios/fastlane/Fastfile
default_platform(:ios)
platform :ios do
desc "Build and upload to TestFlight"
lane :beta do
build_app(
workspace: "Runner.xcworkspace",
scheme: "Runner",
export_method: "app-store",
)
upload_to_testflight(
skip_waiting_for_build_processing: true,
)
end
desc "Take screenshots"
lane :screenshots do
capture_screenshots(
scheme: "Runner",
devices: ["iPhone 15 Pro", "iPad Pro (12.9-inch)"],
)
end
end
> Saída: Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.
# android/fastlane/Fastfile
default_platform(:android)
platform :android do
desc "Build and upload to Play Console"
lane :beta do
gradle(task: "bundleRelease")
upload_to_play_store(
track: "internal",
aab: "../build/app/outputs/bundle/release/app-release.aab",
)
end
end
> Saída: Execute localmente com o Flutter SDK (Flutter 3.x / Dart 3.x). O servidor Piston não possui o Flutter instalado; use `flutter run` na sua máquina local para comparação. A UI/estado real pode variar ligeiramente entre plataformas.
7. Exemplo Completo: Configuração CI/CD do ShopApp
# .github/workflows/release.yml
name: Release ShopApp
on:
push:
tags: ['v*.*.*']
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: true
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with: { flutter-version: '3.x', channel: 'stable' }
- run: flutter pub get
- run: flutter analyze --no-fatal-infos
- run: flutter test --coverage
- uses: codecov/codecov-action@v3
build-android:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with: { flutter-version: '3.x' }
- run: flutter pub get
- name: Decode keystore
run: echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 --decode > android/app/keystore.jks
- name: Build AAB
run: flutter build appbundle --release
env:
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
- name: Upload AAB artifact
uses: actions/upload-artifact@v4
with: { name: app-release-aab, path: build/app/outputs/bundle/release/app-release.aab }
build-ios:
needs: test
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with: { flutter-version: '3.x' }
- run: flutter pub get
- run: cd ios && pod install
- name: Build IPA
run: flutter build ipa --release --export-options-plist=ios/ExportOptions.plist
- uses: actions/upload-artifact@v4
with: { name: app-release-ipa, path: build/ios/ipa/*.ipa }
build-web:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with: { flutter-version: '3.x' }
- run: flutter pub get
- run: flutter build web --wasm --base-href "/app/"
- uses: actions/upload-artifact@v4
with: { name: web-release, path: build/web/ }
release:
needs: [build-android, build-ios, build-web]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Create GitHub Release
uses: softprops/action-gh-release@v1
with:
generate_release_notes: true
draft: false
- name: Slack notify
uses: slackapi/slack-github-action@v1
with:
payload: '{"text": "ShopApp ${{ github.ref_name }} lançado em todas as plataformas!"}'
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}
❓ Perguntas Frequentes
P: O plano gratuito do GitHub Actions é suficiente? R: Projetos de código aberto têm minutos ilimitados. Projetos privados têm 2000 minutos/mês — um build multiplataforma completo leva cerca de 30-40 minutos, então aproximadamente 50 builds por mês.
P: Builds iOS devem usar um runner macOS? R: Sim, o Xcode só roda em macOS. O GitHub fornece runners macOS, mas a cobrança é 10x a taxa do Linux. Use Codemagic para economizar minutos de macOS.
P: Como devo armazenar chaves de assinatura com segurança? R: Use GitHub Secrets (codifique keystore/certificados em Base64), nunca os comita no repositório. Ative o Secret scanning.
P: Como acelerar builds Flutter com cache? R: Use
actions/cachepara cachear pub-cache e gradle-cache — o tempo de build cai de 5 minutos para 2 minutos.
P: Como Fastlane e GitHub Actions trabalham juntos? R: GitHub Actions dispara o build, Fastlane cuida da assinatura e upload. Fastlane roda mais convenientemente em um runner macOS.
P: Como reverter uma versão em produção? R: O Google Play Console suporta reverter para uma versão anterior; a App Store requer o envio de uma nova versão. CI/CD deve reter todos os artefatos de build históricos.
📖 Resumo
- GitHub Actions é a primeira escolha para CI/CD Flutter — configuração YAML + builds multi-matriz
- Assinatura Android usa keystore codificado em Base64 armazenado no GitHub Secrets
- Builds iOS requerem um runner macOS; certificados são armazenados no Secrets
- Fastlane automatiza screenshots e uploads para TestFlight/Play Console
- Pipeline completo: teste → build → assinatura → upload → notificação
📝 Exercícios
- Básico (dificuldade ⭐): Configure um workflow de verificação de PR no GitHub Actions: flutter analyze + flutter test.
- Intermediário (dificuldade ⭐⭐): Adicione um passo de build Android AAB, gerencie chaves de assinatura com GitHub Secrets, e faça upload do artefato após o build.
- Desafio (dificuldade ⭐⭐⭐): Implemente um pipeline CI/CD completo: verificação de PR (lint + teste) + builds multiplataforma acionados por tag (Android AAB + iOS IPA + Web) + assinatura automática + GitHub Release + notificação Slack.