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>
This commit is contained in:
184
client_app/BUILD_FLAVORS.md
Normal file
184
client_app/BUILD_FLAVORS.md
Normal file
@@ -0,0 +1,184 @@
|
||||
# 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}.dart` — `iosBundleId` 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.plist` — `CFBundleURLName` 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
|
||||
```bash
|
||||
# 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
|
||||
```bash
|
||||
# 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)
|
||||
```bash
|
||||
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:**
|
||||
```bash
|
||||
adb shell setprop debug.firebase.analytics.app com.mybestie.dev
|
||||
```
|
||||
- **adb commands** (force-stop, pm clear, am start, grant, etc.):
|
||||
```bash
|
||||
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:
|
||||
```bash
|
||||
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.kts` — `flavorDimensions` + `productFlavors`
|
||||
- `android/app/src/main/AndroidManifest.xml` — `android:label="@string/app_name"`
|
||||
- `lib/main.dart` — `bootstrap()` + 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
|
||||
Reference in New Issue
Block a user