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:
@@ -1,26 +1,18 @@
|
||||
// File generated by FlutterFire CLI.
|
||||
// File generated by FlutterFire CLI (regenerated from the registered
|
||||
// dev Firebase apps — project halobestie-clone-dev).
|
||||
// ignore_for_file: type=lint
|
||||
import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
|
||||
import 'package:flutter/foundation.dart'
|
||||
show defaultTargetPlatform, kIsWeb, TargetPlatform;
|
||||
|
||||
/// Default [FirebaseOptions] for use with your Firebase apps.
|
||||
///
|
||||
/// Example:
|
||||
/// ```dart
|
||||
/// import 'firebase_options.dart';
|
||||
/// // ...
|
||||
/// await Firebase.initializeApp(
|
||||
/// options: DefaultFirebaseOptions.currentPlatform,
|
||||
/// );
|
||||
/// ```
|
||||
class DefaultFirebaseOptions {
|
||||
/// [FirebaseOptions] for the DEV environment (project halobestie-clone-dev).
|
||||
class DevFirebaseOptions {
|
||||
static FirebaseOptions get currentPlatform {
|
||||
if (kIsWeb) {
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions have not been configured for web - '
|
||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||
);
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions have not been configured for web - '
|
||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||
);
|
||||
}
|
||||
switch (defaultTargetPlatform) {
|
||||
case TargetPlatform.android:
|
||||
@@ -51,7 +43,7 @@ class DefaultFirebaseOptions {
|
||||
|
||||
static const FirebaseOptions android = FirebaseOptions(
|
||||
apiKey: 'AIzaSyDFWlLSWytqwI7LSdUbVrO7J5De9L2LV2U',
|
||||
appId: '1:1068156046511:android:ba6e699216de1c50b8185a',
|
||||
appId: '1:1068156046511:android:1f589ed358ccdad0b8185a',
|
||||
messagingSenderId: '1068156046511',
|
||||
projectId: 'halobestie-clone-dev',
|
||||
storageBucket: 'halobestie-clone-dev.firebasestorage.app',
|
||||
@@ -59,10 +51,10 @@ class DefaultFirebaseOptions {
|
||||
|
||||
static const FirebaseOptions ios = FirebaseOptions(
|
||||
apiKey: 'AIzaSyAQnB5hbj0T5tE4JQZQ9Tx6Whp_u15obMI',
|
||||
appId: '1:1068156046511:ios:c7786cedb9101d34b8185a',
|
||||
appId: '1:1068156046511:ios:bc9098ffc2c2913ab8185a',
|
||||
messagingSenderId: '1068156046511',
|
||||
projectId: 'halobestie-clone-dev',
|
||||
storageBucket: 'halobestie-clone-dev.firebasestorage.app',
|
||||
iosBundleId: 'com.mybestie',
|
||||
iosBundleId: 'com.asc.hallobestie.dev',
|
||||
);
|
||||
}
|
||||
61
client_app/lib/firebase/firebase_options_prod.dart
Normal file
61
client_app/lib/firebase/firebase_options_prod.dart
Normal file
@@ -0,0 +1,61 @@
|
||||
// File generated by FlutterFire CLI (regenerated from the registered
|
||||
// prod Firebase apps — project my-bestie-production).
|
||||
// ignore_for_file: type=lint
|
||||
import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
|
||||
import 'package:flutter/foundation.dart'
|
||||
show defaultTargetPlatform, kIsWeb, TargetPlatform;
|
||||
|
||||
/// [FirebaseOptions] for the PROD environment (project my-bestie-production).
|
||||
class ProdFirebaseOptions {
|
||||
static FirebaseOptions get currentPlatform {
|
||||
if (kIsWeb) {
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions have not been configured for web - '
|
||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||
);
|
||||
}
|
||||
switch (defaultTargetPlatform) {
|
||||
case TargetPlatform.android:
|
||||
return android;
|
||||
case TargetPlatform.iOS:
|
||||
return ios;
|
||||
case TargetPlatform.macOS:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions have not been configured for macos - '
|
||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||
);
|
||||
case TargetPlatform.windows:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions have not been configured for windows - '
|
||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||
);
|
||||
case TargetPlatform.linux:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions have not been configured for linux - '
|
||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||
);
|
||||
default:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions are not supported for this platform.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static const FirebaseOptions android = FirebaseOptions(
|
||||
apiKey: 'AIzaSyAxAp8hcXO-P7HwwVsS3vFe0OX5ZkIyyWI',
|
||||
appId: '1:953866659887:android:55dfbf97ac7c26e7183eda',
|
||||
messagingSenderId: '953866659887',
|
||||
projectId: 'my-bestie-production',
|
||||
storageBucket: 'my-bestie-production.firebasestorage.app',
|
||||
);
|
||||
|
||||
static const FirebaseOptions ios = FirebaseOptions(
|
||||
apiKey: 'AIzaSyA8guPSD87eDLeCsH6jVd1n2_SI4_MaGNE',
|
||||
appId: '1:953866659887:ios:159fd11b1d2f3633183eda',
|
||||
messagingSenderId: '953866659887',
|
||||
projectId: 'my-bestie-production',
|
||||
storageBucket: 'my-bestie-production.firebasestorage.app',
|
||||
iosClientId: '953866659887-bsb3c2a6ir10u47q8vcacre2tmnk59jb.apps.googleusercontent.com',
|
||||
iosBundleId: 'com.asc.hallobestie',
|
||||
);
|
||||
}
|
||||
61
client_app/lib/firebase/firebase_options_staging.dart
Normal file
61
client_app/lib/firebase/firebase_options_staging.dart
Normal file
@@ -0,0 +1,61 @@
|
||||
// File generated by FlutterFire CLI (regenerated from the registered
|
||||
// staging Firebase apps — project my-bestie-876ec).
|
||||
// ignore_for_file: type=lint
|
||||
import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
|
||||
import 'package:flutter/foundation.dart'
|
||||
show defaultTargetPlatform, kIsWeb, TargetPlatform;
|
||||
|
||||
/// [FirebaseOptions] for the STAGING environment (project my-bestie-876ec).
|
||||
class StagingFirebaseOptions {
|
||||
static FirebaseOptions get currentPlatform {
|
||||
if (kIsWeb) {
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions have not been configured for web - '
|
||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||
);
|
||||
}
|
||||
switch (defaultTargetPlatform) {
|
||||
case TargetPlatform.android:
|
||||
return android;
|
||||
case TargetPlatform.iOS:
|
||||
return ios;
|
||||
case TargetPlatform.macOS:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions have not been configured for macos - '
|
||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||
);
|
||||
case TargetPlatform.windows:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions have not been configured for windows - '
|
||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||
);
|
||||
case TargetPlatform.linux:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions have not been configured for linux - '
|
||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||
);
|
||||
default:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions are not supported for this platform.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static const FirebaseOptions android = FirebaseOptions(
|
||||
apiKey: 'AIzaSyAOPkPJkHXLFzo9ICOHyjee2Vn_EUqt1Pc',
|
||||
appId: '1:650461407929:android:05754df9552e0529504968',
|
||||
messagingSenderId: '650461407929',
|
||||
projectId: 'my-bestie-876ec',
|
||||
storageBucket: 'my-bestie-876ec.firebasestorage.app',
|
||||
);
|
||||
|
||||
static const FirebaseOptions ios = FirebaseOptions(
|
||||
apiKey: 'AIzaSyC_BewS88iaNsc9QdwsPzkV0sf9wUs4i_4',
|
||||
appId: '1:650461407929:ios:4ee79d479b69d688504968',
|
||||
messagingSenderId: '650461407929',
|
||||
projectId: 'my-bestie-876ec',
|
||||
storageBucket: 'my-bestie-876ec.firebasestorage.app',
|
||||
iosClientId: '650461407929-fb4t48nmguaslfvis7ebsea8vndro7ac.apps.googleusercontent.com',
|
||||
iosBundleId: 'com.asc.hallobestie.staging',
|
||||
);
|
||||
}
|
||||
@@ -13,10 +13,23 @@ import 'core/chat/chat_notifier.dart';
|
||||
import 'core/notifications/notification_service.dart';
|
||||
import 'core/pairing/pairing_notifier.dart';
|
||||
import 'core/theme/halo_theme.dart';
|
||||
import 'firebase_options.dart';
|
||||
import 'firebase/firebase_options_dev.dart';
|
||||
import 'router.dart';
|
||||
|
||||
void main() async {
|
||||
/// Shared app bootstrap, parameterised per build flavor.
|
||||
///
|
||||
/// The flavor entrypoints (`main_dev.dart`, `main_staging.dart`,
|
||||
/// `main_prod.dart`) each call this with their environment's
|
||||
/// [FirebaseOptions] and a [flavor] tag. The bare [main] below delegates to
|
||||
/// dev so a plain `flutter run` (no `-t`) still launches the dev environment.
|
||||
///
|
||||
/// `flavor` is currently informational (kept on hand for future flavor-gated
|
||||
/// behaviour / analytics tagging); the API base URL is supplied separately via
|
||||
/// `--dart-define-from-file=env/<flavor>.json` (see BUILD_FLAVORS.md).
|
||||
Future<void> bootstrap({
|
||||
required FirebaseOptions firebaseOptions,
|
||||
required String flavor,
|
||||
}) async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
// Pre-warm flutter_secure_storage. The first call triggers AndroidX
|
||||
@@ -26,7 +39,7 @@ void main() async {
|
||||
// splash instead of paying it on the user's first interaction.
|
||||
unawaited(TokenStorage().readRefreshToken());
|
||||
|
||||
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
|
||||
await Firebase.initializeApp(options: firebaseOptions);
|
||||
|
||||
final messaging = FirebaseMessaging.instance;
|
||||
await messaging.requestPermission();
|
||||
@@ -34,6 +47,16 @@ void main() async {
|
||||
runApp(const ProviderScope(child: App()));
|
||||
}
|
||||
|
||||
void main() async {
|
||||
// Bare `flutter run` (no `-t lib/main_<flavor>.dart`) defaults to dev so
|
||||
// local development works out of the box. Build-flavor APKs use the
|
||||
// flavor-specific entrypoints instead.
|
||||
await bootstrap(
|
||||
firebaseOptions: DevFirebaseOptions.currentPlatform,
|
||||
flavor: 'dev',
|
||||
);
|
||||
}
|
||||
|
||||
class App extends ConsumerStatefulWidget {
|
||||
const App({super.key});
|
||||
|
||||
|
||||
14
client_app/lib/main_dev.dart
Normal file
14
client_app/lib/main_dev.dart
Normal file
@@ -0,0 +1,14 @@
|
||||
import 'firebase/firebase_options_dev.dart';
|
||||
import 'main.dart';
|
||||
|
||||
/// DEV flavor entrypoint.
|
||||
///
|
||||
/// Run/build with the matching flavor + env file:
|
||||
/// flutter run --flavor dev -t lib/main_dev.dart \
|
||||
/// --dart-define-from-file=env/dev.json
|
||||
void main() {
|
||||
bootstrap(
|
||||
firebaseOptions: DevFirebaseOptions.currentPlatform,
|
||||
flavor: 'dev',
|
||||
);
|
||||
}
|
||||
14
client_app/lib/main_prod.dart
Normal file
14
client_app/lib/main_prod.dart
Normal file
@@ -0,0 +1,14 @@
|
||||
import 'firebase/firebase_options_prod.dart';
|
||||
import 'main.dart';
|
||||
|
||||
/// PROD flavor entrypoint.
|
||||
///
|
||||
/// Run/build with the matching flavor + env file:
|
||||
/// flutter build apk --flavor prod -t lib/main_prod.dart \
|
||||
/// --dart-define-from-file=env/prod.json
|
||||
void main() {
|
||||
bootstrap(
|
||||
firebaseOptions: ProdFirebaseOptions.currentPlatform,
|
||||
flavor: 'prod',
|
||||
);
|
||||
}
|
||||
14
client_app/lib/main_staging.dart
Normal file
14
client_app/lib/main_staging.dart
Normal file
@@ -0,0 +1,14 @@
|
||||
import 'firebase/firebase_options_staging.dart';
|
||||
import 'main.dart';
|
||||
|
||||
/// STAGING flavor entrypoint.
|
||||
///
|
||||
/// Run/build with the matching flavor + env file:
|
||||
/// flutter run --flavor staging -t lib/main_staging.dart \
|
||||
/// --dart-define-from-file=env/staging.json
|
||||
void main() {
|
||||
bootstrap(
|
||||
firebaseOptions: StagingFirebaseOptions.currentPlatform,
|
||||
flavor: 'staging',
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user