Files
halobestie-clone/client_app/BUILD_FLAVORS.md
Ramadhan Sjamsani 22743c81e1 feat(build): add dev/staging/prod flavors for client_app + mitra_app
Android product flavors (.dev/.staging suffixes, prod clean) + per-flavor
Dart entrypoints, dart-define env files, and per-flavor Firebase config for
both platforms across 3 projects (halobestie-clone-dev / my-bestie-876ec /
my-bestie-production).

- Android: flavorDimensions("env") + productFlavors; @string/app_name label;
  per-flavor src/<flavor>/google-services.json (clients verified to match each
  applicationId).
- iOS: customer app re-based to the EXISTING App Store identity
  com.asc.hallobestie (dev/staging suffix it; ships as an update to the live
  app). mitra is a new app (com.mybestie.mitra). Per-flavor plists staged in
  ios/config/<flavor>/; Xcode scheme wiring deferred (Mac follow-up).
- firebase_options_{dev,staging,prod}.dart filled with real android + iOS
  values (regenerated from the native config files).
- BUILD_FLAVORS.md per app documents flavor table, build commands, iOS
  identity decision, and the remaining iOS Xcode steps.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 22:21:50 +08:00

9.4 KiB

Build Flavors — client_app (Android + iOS identity)

Three build flavors map each environment to its own app ID, backend URL, app display name, and Firebase config. This lets dev / staging / prod builds sit side-by-side on one device (distinct package / bundle IDs).

Scope: Android flavors + Dart + env files are fully wired. On iOS, only the bundle identity is set (see the iOS section below); the iOS Xcode build-configuration + scheme wiring (per-flavor bundle suffixing, per-scheme GoogleService-Info.plist) is a separate follow-up that needs a Mac and is NOT done here.

Flavor table

Flavor applicationId App name API_BASE_URL
dev com.mybestie.dev HaloBestie Dev http://192.168.88.247:3000
staging com.mybestie.staging HaloBestie Staging https://staging-api.halobestie.com ⚠️*
prod com.mybestie HaloBestie https://api.halobestie.com

* staging URL is a PLACEHOLDER — confirm the real staging host and update env/staging.json (see the _TODO key there).

The applicationId is set in android/app/build.gradle.kts via applicationIdSuffix (dev/staging) on top of the base com.mybestie; prod has no suffix. The app name comes from a per-flavor resValue("string","app_name",…) consumed by android:label="@string/app_name" in AndroidManifest.xml.

iOS bundle identity — DIFFERENT base from Android ⚠️

The customer app already exists on the App Store under bundle ID com.asc.hallobestie (published by a prior vendor — note the asc prefix and hallobestie spelling). Decision: v2 ships as an update to that existing listing, so the iOS prod bundle ID must stay com.asc.hallobestie (you cannot change a published app's bundle ID without it becoming a new app). The iOS flavors therefore suffix that base, NOT the Android com.mybestie base:

Flavor Android applicationId iOS bundle ID
dev com.mybestie.dev com.asc.hallobestie.dev
staging com.mybestie.staging com.asc.hallobestie.staging
prod com.mybestie com.asc.hallobestie

A bundle ID differing across platforms is normal. What's done so far on iOS:

  • ios/Runner.xcodeproj/project.pbxproj — base PRODUCT_BUNDLE_IDENTIFIER set to com.asc.hallobestie (+ com.asc.hallobestie.RunnerTests). Once iOS schemes are wired, dev/staging suffix off this base.
  • firebase_options_{dev,staging,prod}.dartiosBundleId set to the table above. dev's iOS appId/apiKey are now PLACEHOLDERs (the old com.mybestie iOS app no longer matches) — register com.asc.hallobestie.dev in the dev project and paste the values.
  • ios/Runner/Info.plistCFBundleURLName aligned to com.asc.hallobestie (the halobestie:// deeplink scheme itself is unchanged).

Still TODO on iOS (the deferred Mac/Xcode work):

  • The existing ios/Runner/GoogleService-Info.plist still lists com.mybestie — replace per flavor once the iOS apps are registered.
  • Xcode build configs + schemes to actually apply the .dev/.staging bundle suffixes per build, with a copy-script selecting the right GoogleService-Info.plist.
  • You need access to the asc Apple Developer account + its signing certs/provisioning profiles to release the prod update.

mitra app is unaffected — it's a brand-new app (com.mybestie.mitra + suffixes) on both platforms; only the customer iOS app inherits the legacy com.asc.hallobestie identity.

How the pieces fit

Concern dev staging prod
Dart entrypoint lib/main_dev.dart lib/main_staging.dart lib/main_prod.dart
Firebase Dart opts lib/firebase/firebase_options_dev.dart …_staging.dart (PLACEHOLDER) …_prod.dart (PLACEHOLDER)
Android Firebase cfg android/app/src/dev/google-services.json android/app/src/staging/… (MISSING) android/app/src/prod/… (MISSING)
Env / dart-define env/dev.json env/staging.json env/prod.json

A bare flutter run (no -t) still works and defaults to dev: lib/main.dart's main() delegates to bootstrap(flavor: 'dev', …).

Run / build commands

Each command needs three things: --flavor <f>, -t lib/main_<f>.dart, and --dart-define-from-file=env/<f>.json.

Run on a connected device/emulator

# dev
flutter run --flavor dev     -t lib/main_dev.dart     --dart-define-from-file=env/dev.json

# staging
flutter run --flavor staging -t lib/main_staging.dart --dart-define-from-file=env/staging.json

# prod
flutter run --flavor prod    -t lib/main_prod.dart    --dart-define-from-file=env/prod.json

Build a release APK

# dev
flutter build apk --flavor dev     -t lib/main_dev.dart     --dart-define-from-file=env/dev.json

# staging
flutter build apk --flavor staging -t lib/main_staging.dart --dart-define-from-file=env/staging.json

# prod
flutter build apk --flavor prod    -t lib/main_prod.dart    --dart-define-from-file=env/prod.json

Build an App Bundle (Play release)

flutter build appbundle --flavor prod -t lib/main_prod.dart --dart-define-from-file=env/prod.json

Output APKs land in build/app/outputs/flutter-apk/app-<flavor>-release.apk.

⚠️ CRITICAL warnings

(a) Every build/install/run command now needs --flavor

Once product flavors exist, a bare flutter build apk / flutter install / flutter run with no --flavor FAILS (Gradle can't pick a flavor). All build, install, and run invocations — CI scripts, local muscle memory, IDE launch configs — must pass --flavor <dev|staging|prod> plus the matching -t lib/main_<flavor>.dart and --dart-define-from-file=env/<flavor>.json.

(b) The dev applicationId is now com.mybestie.dev, not com.mybestie

Anything that references the package name by string must be updated for dev:

  • GA4 DebugView:
    adb shell setprop debug.firebase.analytics.app com.mybestie.dev
    
  • adb commands (force-stop, pm clear, am start, grant, etc.):
    adb shell am force-stop com.mybestie.dev
    adb shell pm clear com.mybestie.dev
    
  • Maestro flows: any appId: com.mybestie must become appId: com.mybestie.dev for the dev build. Check .maestro/ configs and per-flow appId headers.

Staging uses com.mybestie.staging; prod stays com.mybestie.

Firebase config — STATUS: configured (2026-06-04)

All apps are registered and config files are in place for both platforms, across 3 projects (one Firebase project per env):

Env Firebase project Android applicationId iOS bundle ID
dev halobestie-clone-dev com.mybestie.dev com.asc.hallobestie.dev
staging my-bestie-876ec com.mybestie.staging com.asc.hallobestie.staging
prod my-bestie-production com.mybestie com.asc.hallobestie

In place and verified:

  • android/app/src/<flavor>/google-services.json — all 3, each containing a client matching the flavor applicationId.
  • ios/config/<flavor>/GoogleService-Info.plist — all 3, bundle IDs verified.
  • lib/firebase/firebase_options_{dev,staging,prod}.dart — real android + iOS values (regenerated from the native files above; no placeholders left).

Regenerating after any ID / key change

Either re-run flutterfire per flavor, or re-download the native files and re-extract. Project IDs:

flutterfire configure --project=halobestie-clone-dev  --out=lib/firebase/firebase_options_dev.dart
flutterfire configure --project=my-bestie-876ec       --out=lib/firebase/firebase_options_staging.dart
flutterfire configure --project=my-bestie-production  --out=lib/firebase/firebase_options_prod.dart

Still TODO — iOS only (Mac/Xcode)

  • iOS Xcode schemes + build-phase copy script to apply per-flavor bundle suffixes and select the right GoogleService-Info.plist at build time. Until then, iOS bundles only ios/Runner/GoogleService-Info.plist.
  • Replace ios/Runner/GoogleService-Info.plist (still the legacy com.mybestie dev file) — or let the copy script overwrite it per build.
  • Prod iOS release needs the asc Apple Developer account + signing.

Files in this setup

  • android/app/build.gradle.ktsflavorDimensions + productFlavors
  • android/app/src/main/AndroidManifest.xmlandroid:label="@string/app_name"
  • lib/main.dartbootstrap() + dev-default main()
  • lib/main_dev.dart / lib/main_staging.dart / lib/main_prod.dart
  • lib/firebase/firebase_options_{dev,staging,prod}.dart
  • env/{dev,staging,prod}.json
  • android/app/src/{dev,staging,prod}/ Firebase config source sets