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>
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— basePRODUCT_BUNDLE_IDENTIFIERset tocom.asc.hallobestie(+com.asc.hallobestie.RunnerTests). Once iOS schemes are wired, dev/staging suffix off this base.firebase_options_{dev,staging,prod}.dart—iosBundleIdset to the table above. dev's iOS appId/apiKey are now PLACEHOLDERs (the oldcom.mybestieiOS app no longer matches) — registercom.asc.hallobestie.devin the dev project and paste the values.ios/Runner/Info.plist—CFBundleURLNamealigned tocom.asc.hallobestie(thehalobestie://deeplink scheme itself is unchanged).
Still TODO on iOS (the deferred Mac/Xcode work):
- The existing
ios/Runner/GoogleService-Info.pliststill listscom.mybestie— replace per flavor once the iOS apps are registered. - Xcode build configs + schemes to actually apply the
.dev/.stagingbundle suffixes per build, with a copy-script selecting the rightGoogleService-Info.plist. - You need access to the
ascApple 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 legacycom.asc.hallobestieidentity.
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.mybestiemust becomeappId: com.mybestie.devfor the dev build. Check.maestro/configs and per-flowappIdheaders.
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 flavorapplicationId.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.plistat build time. Until then, iOS bundles onlyios/Runner/GoogleService-Info.plist. - Replace
ios/Runner/GoogleService-Info.plist(still the legacycom.mybestiedev file) — or let the copy script overwrite it per build. - Prod iOS release needs the
ascApple Developer account + signing.
Files in this setup
android/app/build.gradle.kts—flavorDimensions+productFlavorsandroid/app/src/main/AndroidManifest.xml—android:label="@string/app_name"lib/main.dart—bootstrap()+ dev-defaultmain()lib/main_dev.dart/lib/main_staging.dart/lib/main_prod.dartlib/firebase/firebase_options_{dev,staging,prod}.dartenv/{dev,staging,prod}.jsonandroid/app/src/{dev,staging,prod}/Firebase config source sets