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

185 lines
9.4 KiB
Markdown

# 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