Ada satu ritual yang dulu selalu saya lakukan setiap mau rilis versi baru aplikasi: buka laptop, pastikan environment benar, jalankan build, tunggu, sign APK manual, upload ke Play Console, isi release notes. Kalau ada yang salah, ulangi dari awal.
Proses itu memakan waktu 45β90 menit setiap rilis. Dan karena sering melelahkan, saya jadi menunda rilis β yang artinya bug fix yang sudah selesai dua minggu lalu baru sampai ke pengguna tiga minggu kemudian.
Sekarang, setiap kali saya push tag ke GitHub, pipeline otomatis menjalankan test, membangun APK yang sudah di-sign, dan mengupload ke Play Store internal track. Tanpa saya sentuh apapun. Artikel ini adalah panduan lengkap untuk setup yang sama.
Gambaran Besar Pipeline
Pipeline yang akan kita bangun punya dua workflow:
Workflow 1 β CI (setiap push dan pull request):
- Checkout kode
- Setup Flutter
- Install dependencies
- Run analyzer (dart analyze)
- Run semua test
Workflow 2 β CD (hanya saat push tag):
- Semua langkah CI
- Build release APK dan App Bundle
- Sign dengan keystore production
- Upload ke Google Play internal track
Langkah 1: Simpan Secrets di GitHub
Sebelum menulis workflow, kita perlu menyiapkan secrets. Buka repository di GitHub β Settings β Secrets and variables β Actions.
Tambahkan secrets berikut:
KEYSTORE_BASE64β file keystore Anda, dikonversi ke base64KEY_ALIASβ alias key di keystoreKEY_PASSWORDβ password keySTORE_PASSWORDβ password keystorePLAY_STORE_JSON_KEYβ service account JSON dari Google Play Console
Untuk mengkonversi keystore ke base64:
base64 -i keystore-produksi.jks | pbcopy # macOS
base64 -w 0 keystore-produksi.jks # Linux
Langkah 2: Workflow CI
Buat file .github/workflows/ci.yml:
name: CI
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: '3.19.0'
channel: 'stable'
cache: true
- name: Install dependencies
run: flutter pub get
- name: Verify formatting
run: dart format --output=none --set-exit-if-changed .
- name: Analyze
run: flutter analyze --no-fatal-infos
- name: Run tests
run: flutter test --coverage
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
file: coverage/lcov.info
Parameter cache: true di flutter-action sangat penting β ia mengcache Flutter SDK dan pub cache antara run. Tanpa ini, setiap run mengunduh Flutter dari awal (Β±800 MB), yang bisa menambah 3β5 menit ke setiap run.
Langkah 3: Workflow CD
Buat file .github/workflows/cd.yml:
name: CD
on:
push:
tags:
- 'v*'
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: '3.19.0'
channel: 'stable'
cache: true
- name: Install dependencies
run: flutter pub get
- name: Run tests
run: flutter test
- name: Setup keystore
run: |
echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 --decode > android/app/keystore.jks
echo "storePassword=${{ secrets.STORE_PASSWORD }}" >> android/key.properties
echo "keyPassword=${{ secrets.KEY_PASSWORD }}" >> android/key.properties
echo "keyAlias=${{ secrets.KEY_ALIAS }}" >> android/key.properties
echo "storeFile=keystore.jks" >> android/key.properties
- name: Build App Bundle
run: |
flutter build appbundle --release
--obfuscate
--split-debug-info=build/debug-info
- name: Upload to Play Store
uses: r0adkll/upload-google-play@v1
with:
serviceAccountJsonPlainText: ${{ secrets.PLAY_STORE_JSON_KEY }}
packageName: com.alfakwt.namaaplikasi
releaseFiles: build/app/outputs/bundle/release/*.aab
track: internal
status: completed
- name: Cleanup keystore
if: always()
run: |
rm -f android/app/keystore.jks
rm -f android/key.properties
Perhatikan langkah "Cleanup keystore" yang menggunakan if: always() β ini memastikan file keystore selalu dihapus dari runner, bahkan jika langkah sebelumnya gagal.
Langkah 4: Setup key.properties di build.gradle
Di android/app/build.gradle, tambahkan konfigurasi signing:
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}
android {
signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile keystoreProperties['storeFile'] ?
file(keystoreProperties['storeFile']) : null
storePassword keystoreProperties['storePassword']
}
}
buildTypes {
release {
signingConfig signingConfigs.release
minifyEnabled true
shrinkResources true
}
}
}
Dan pastikan key.properties ada di .gitignore β jangan pernah commit file ini ke repository.
Langkah 5: Service Account untuk Play Store
Ini adalah langkah yang paling sering membingungkan. Untuk upload otomatis ke Play Store, Anda butuh service account Google Cloud dengan izin yang tepat.
Langkah-langkahnya:
- Buka Google Play Console
- Setup β API access β Link ke Google Cloud project
- Di Google Cloud Console, buat service account baru
- Download file JSON credential-nya
- Kembali ke Play Console, grant izin ke service account tersebut (minimal "Release to internal testing")
- Simpan isi JSON tersebut sebagai secret
PLAY_STORE_JSON_KEY
Memicu Release: Konvensi Tag
Workflow CD aktif ketika ada push tag yang dimulai dengan "v". Konvensi yang saya gunakan:
# Rilis production
git tag -a v1.2.0 -m "Release 1.2.0: fitur pencarian dan perbaikan crash"
git push origin v1.2.0
# Internal testing
git tag -a v1.2.0-beta.1 -m "Beta 1.2.0: uji fitur pencarian"
git push origin v1.2.0-beta.1
Anda bisa memodifikasi workflow untuk memilih track Play Store (internal, alpha, beta, production) berdasarkan apakah tag mengandung "beta" atau tidak.
Berapa Lama Pipeline Berjalan?
Dari pengalaman di proyek saya sendiri:
- CI (test saja): 4β6 menit
- CD lengkap (test + build + upload): 12β18 menit
Dengan caching yang tepat, angka ini relatif stabil dan tidak terlalu dipengaruhi ukuran proyek selama dependency tidak berubah banyak.
Kesimpulan
Setup CI/CD membutuhkan investasi waktu di depan β mungkin setengah hari untuk pertama kali. Tapi setelah berjalan, ia menghemat puluhan jam per tahun, mengurangi human error di proses rilis, dan yang paling penting: membuat rilis menjadi sesuatu yang tidak Anda tunda lagi.
Ketika rilis semudah push sebuah tag, Anda akan rilis lebih sering. Rilis lebih sering berarti iterasi lebih cepat. Dan iterasi lebih cepat berarti produk yang lebih baik.
Semua dimulai dari satu file YAML.
Belum ada komentar. Jadilah yang pertama menulis.
Tulis Komentar