Best Practice #
Sebuah release yang baik adalah yang tidak terasa seperti event besar — ia terjadi secara rutin, dapat diprediksi, dan mudah dibalik jika ada masalah. Artikel ini merangkum praktik-praktik yang membuat proses build dan release menjadi aman dan berkelanjutan.
1. Versi Otomatis dari Git Tag #
Daripada mengubah versi di pubspec.yaml secara manual, otomasi dari Git tag:
# Alur:
# 1. Developer push tag: git tag v2.3.1 && git push origin v2.3.1
# 2. CI otomatis baca tag dan gunakan sebagai versi
# 3. Build number dari nomor run CI
# Di GitHub Actions
- name: Extract version dari tag
if: startsWith(github.ref, 'refs/tags/v')
id: version
run: |
# Ambil versi dari tag (v2.3.1 → 2.3.1)
VERSION=${GITHUB_REF#refs/tags/v}
# Build number dari GitHub run number (selalu naik)
BUILD_NUMBER=${{ github.run_number }}
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "build_number=$BUILD_NUMBER" >> $GITHUB_OUTPUT
- name: Update pubspec.yaml version
run: |
sed -i "s/^version:.*/version: ${{ steps.version.outputs.version }}+${{ steps.version.outputs.build_number }}/" pubspec.yaml
2. Staged Rollout — Rilis Bertahap #
Jangan langsung rilis ke 100% pengguna. Rilis bertahap memungkinkan deteksi masalah sebelum seluruh pengguna terdampak:
Play Store Staged Rollout:
5% → tunggu 24 jam, cek crash rate dan ANR rate
25% → tunggu 24 jam
50% → tunggu 24 jam
100% → full release
Batas aman untuk lanjut ke tahap berikutnya:
✓ Crash rate < 0.5% (batas Play Store: <1% untuk "good standing")
✓ ANR rate < 0.2%
✓ Rating rata-rata tidak turun signifikan
✓ Tidak ada laporan bug kritis dari tester di tahap sebelumnya
Kapan harus HALT rollout:
✗ Crash rate > 2% (naik drastis dari versi sebelumnya)
✗ Fitur kritis tidak berfungsi (login gagal, crash saat launch)
✗ Data pengguna tidak terbaca atau corrupt
# Di Play Console: Release → Production → Managed publishing
# Ubah rollout percentage kapan saja
3. Monitoring Crash — Sentry atau Firebase Crashlytics #
Jangan rilis tanpa crash monitoring. Kamu tidak akan tahu ada masalah kecuali ada yang melaporkan.
# pubspec.yaml
dependencies:
# Pilih salah satu:
firebase_crashlytics: ^4.2.0 # terintegrasi dengan Firebase
sentry_flutter: ^8.12.0 # standalone, lebih detail
Setup Crashlytics #
// main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
// Tangkap semua error Flutter
FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterFatalError;
// Tangkap error di luar Flutter (async errors)
PlatformDispatcher.instance.onError = (error, stack) {
FirebaseCrashlytics.instance.recordError(error, stack, fatal: true);
return true;
};
runApp(const MyApp());
}
// Tambahkan informasi konteks ke crash report
class AuthNotifier extends Notifier<AuthState> {
Future<void> login(String email, String password) async {
try {
final user = await ref.read(authRepoProvider).login(email, password);
// Set user identifier di crash report
await FirebaseCrashlytics.instance.setUserIdentifier(user.id);
await FirebaseCrashlytics.instance.setCustomKey('email', email);
await FirebaseCrashlytics.instance.setCustomKey('plan', user.plan);
state = AuthState.authenticated(user);
} catch (e, stack) {
// Log error non-fatal
await FirebaseCrashlytics.instance.recordError(e, stack, fatal: false);
state = AuthState.error(e.toString());
}
}
}
Setup Sentry #
// main.dart dengan Sentry
void main() async {
await SentryFlutter.init(
(options) {
options.dsn = AppConfig.sentryDsn;
options.environment = AppConfig.environment.name; // 'development', 'production'
options.release = '${AppConfig.appName}@${AppConfig.version}';
options.tracesSampleRate = AppConfig.isProduction ? 0.1 : 1.0; // 10% di prod
options.enableAutoSessionTracking = true;
},
appRunner: () => runApp(const MyApp()),
);
}
// Tangkap error manual
try {
await riskyOperation();
} catch (exception, stackTrace) {
await Sentry.captureException(
exception,
stackTrace: stackTrace,
withScope: (scope) {
scope.setTag('feature', 'checkout');
scope.setExtra('orderId', orderId);
},
);
}
4. Rollback Plan #
Selalu punya rencana rollback sebelum rilis:
Play Store:
→ Rollback dengan rilis versi sebelumnya
→ Play Store tidak punya "rollback" tombol
→ Harus upload build lama sebagai release baru dengan version baru
→ Simpan semua AAB lama (minimal 3 versi terakhir)!
App Store:
→ Bisa "unpublish" versi saat ini dari App Store Connect
→ Tapi versi yang sudah didownload pengguna tetap berjalan
→ Untuk force update: tandai minimum version di backend
→ Pengguna yang buka app di bawah minimum version ditampilkan dialog wajib update
Force Update Strategy:
// Cek minimum version saat app launch
class AppVersionChecker {
static Future<void> checkMinVersion() async {
final minVersion = await ApiClient.instance.getMinimumVersion();
final currentVersion = AppConfig.version; // dari pubspec
if (isVersionBelow(currentVersion, minVersion)) {
// Tampilkan dialog yang tidak bisa di-dismiss
showForceUpdateDialog();
}
}
static bool isVersionBelow(String current, String minimum) {
final c = current.split('.').map(int.parse).toList();
final m = minimum.split('.').map(int.parse).toList();
for (var i = 0; i < 3; i++) {
if (c[i] < m[i]) return true;
if (c[i] > m[i]) return false;
}
return false;
}
}
5. Release Notes yang Berguna #
ANTI-PATTERN: release notes yang tidak informatif
"Bug fixes and performance improvements"
"Minor updates"
"Version 2.3.1"
BENAR: jelaskan apa yang berubah untuk pengguna
Apa yang baru:
• Fitur filter produk berdasarkan kategori dan harga
• Dukungan dark mode yang bisa diatur di Pengaturan
Perbaikan:
• Checkout tidak lagi gagal saat menggunakan voucher diskon
• Scroll di halaman pesanan kini lebih mulus
• Notifikasi muncul dengan benar setelah restart app
Tips: Tulis untuk pengguna, bukan developer.
Pengguna tidak peduli "fixed null pointer exception" --
mereka peduli "login tidak lagi crash".
Anti-Pattern yang Harus Dihindari #
VERSIONING:
✗ Lupa increment build number -- upload ke store akan ditolak
✗ Menggunakan tanggal sebagai versi (20250115) -- tidak semantik
✗ Skip versi (1.0.0 → 1.0.3) -- membingungkan saat debug
SIGNING:
✗ Commit keystore atau certificate ke git
✗ Gunakan debug keystore untuk release -- tidak bisa upload ke store
✗ Kehilangan keystore tanpa backup -- tidak bisa update app yang ada!
✓ Backup keystore di cloud storage yang aman (multiple copy)
TESTING RELEASE BUILD:
✗ Hanya test di debug mode, release bisa berbeda perilakunya
✗ Tidak test di device target terendah (low-end Android)
✗ Upload langsung ke production tanpa staging test
CI/CD:
✗ Build lokal lalu upload -- manual dan rawan human error
✗ Simpan secret di kode atau environment file yang di-commit
✗ Tidak ada notifikasi saat build gagal
RELEASE:
✗ Rilis pada hari Jumat sore -- tidak ada yang monitor weekend
✓ Rilis Senin-Rabu pagi -- ada waktu untuk monitor dan fix cepat
✗ Rilis bersamaan dengan banyak perubahan besar
✓ Rilis inkremental, perubahan kecil lebih sering
Checklist Akhir Sebelum Publish #
PRE-RELEASE (1-2 hari sebelum):
□ Semua test lulus di CI
□ Build release sudah ditest di device fisik (Android dan iOS)
□ Flow kritis sudah diverifikasi: login, fitur utama, checkout
□ Tidak ada crash yang diketahui
□ Version sudah di-bump di pubspec.yaml
□ CHANGELOG sudah diperbarui
STORE LISTING:
□ Screenshot semua ukuran sudah diperbarui jika ada perubahan UI
□ Release notes sudah ditulis dalam bahasa yang dipahami pengguna
□ Privacy policy URL masih valid
UPLOAD:
□ AAB/IPA diupload ke store yang tepat
□ Staged rollout dikonfigurasi (mulai 5-10%)
□ Crash monitoring aktif dan terkoneksi ke versi baru
POST-RELEASE (24-48 jam setelah rilis):
□ Pantau crash rate di Firebase Crashlytics / Sentry
□ Pantau ANR rate di Play Console
□ Pantau rating dan review di Play Store / App Store
□ Lanjutkan rollout jika tidak ada masalah
□ Siap untuk hotfix jika ada masalah kritis
Ringkasan #
- Otomasi versioning dari Git tag via CI — tidak ada lagi lupa increment build number atau human error saat update pubspec.yaml.
- Staged rollout adalah perlindungan terpenting — mulai dari 5%, pantau crash rate 24 jam, baru lanjutkan. Jangan rilis ke 100% sekaligus untuk versi mayor.
- Crash monitoring wajib sebelum rilis produksi — Crashlytics (gratis, terintegrasi Firebase) atau Sentry (lebih detail, ada free tier). Atur alert untuk crash rate spike.
- Simpan backup keystore di multiple lokasi yang aman (cloud storage, password manager) — kehilangan keystore berarti tidak bisa update app yang sudah di store.
- Jangan rilis Jumat sore — tidak ada yang bisa monitor dan fix masalah saat weekend. Rilis Senin-Rabu pagi adalah waktu terbaik.
- Release notes yang baik menjelaskan perubahan dalam bahasa pengguna, bukan developer — “login tidak lagi crash” bukan “fixed NPE in AuthService”.
- Selalu punya rollback plan — untuk Play Store simpan AAB lama, untuk App Store implementasi force update di sisi server dengan minimum version check.
← Sebelumnya: CI/CD
Selamat! Kamu telah menyelesaikan seluruh Flutter Tutorial Series.