feat(analytics): wire Firebase Analytics into app startup
flutterfire configure registered the iOS/Android apps under project block-seasons and generated firebase_options.dart + native config. main() now initializes Firebase and routes analytics through FirebaseAnalyticsBackend in release builds (console logger in debug, so dev traffic never pollutes GA4). Firebase init is guarded — failure falls back to the debug logger rather than blocking startup. firebase.json keeps the existing Hosting config and gains the FlutterFire platform section. Client config files are committed (they ship in the binary; Firebase security is enforced by rules, not config secrecy). flutter analyze clean, all 161 tests green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
// START: FlutterFire Configuration
|
||||
id("com.google.gms.google-services")
|
||||
// END: FlutterFire Configuration
|
||||
id("kotlin-android")
|
||||
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
||||
id("dev.flutter.flutter-gradle-plugin")
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"project_info": {
|
||||
"project_number": "190209969950",
|
||||
"project_id": "block-seasons",
|
||||
"storage_bucket": "block-seasons.firebasestorage.app"
|
||||
},
|
||||
"client": [
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:190209969950:android:e08dc30877b1821b44c30f",
|
||||
"android_client_info": {
|
||||
"package_name": "com.airkjw.blockseasons"
|
||||
}
|
||||
},
|
||||
"oauth_client": [],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "AIzaSyCd_3Tw5IxlO6ysp3XMQ9tBsOM1yMYl2MU"
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"appinvite_service": {
|
||||
"other_platform_oauth_client": []
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"configuration_version": "1"
|
||||
}
|
||||
@@ -20,6 +20,9 @@ pluginManagement {
|
||||
plugins {
|
||||
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
||||
id("com.android.application") version "8.9.1" apply false
|
||||
// START: FlutterFire Configuration
|
||||
id("com.google.gms.google-services") version("4.4.4") apply false
|
||||
// END: FlutterFire Configuration
|
||||
id("org.jetbrains.kotlin.android") version "2.1.0" apply false
|
||||
}
|
||||
|
||||
|
||||
+1
-10
@@ -1,10 +1 @@
|
||||
{
|
||||
"hosting": {
|
||||
"public": "deploy",
|
||||
"ignore": [
|
||||
"firebase.json",
|
||||
"**/.*",
|
||||
"**/node_modules/**"
|
||||
]
|
||||
}
|
||||
}
|
||||
{"hosting":{"public":"deploy","ignore":["firebase.json","**/.*","**/node_modules/**"]},"flutter":{"platforms":{"android":{"default":{"projectId":"block-seasons","appId":"1:190209969950:android:e08dc30877b1821b44c30f","fileOutput":"android/app/google-services.json"}},"ios":{"default":{"projectId":"block-seasons","appId":"1:190209969950:ios:f9d4578ec86f92c844c30f","uploadDebugSymbols":false,"fileOutput":"ios/Runner/GoogleService-Info.plist"}},"dart":{"lib/firebase_options.dart":{"projectId":"block-seasons","configurations":{"android":"1:190209969950:android:e08dc30877b1821b44c30f","ios":"1:190209969950:ios:f9d4578ec86f92c844c30f"}}}}}}
|
||||
@@ -16,6 +16,7 @@
|
||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||
A1624C49AABB61D3BB6EBA00 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F021B835BC4E346AE82B4C9 /* Pods_RunnerTests.framework */; };
|
||||
D444497F007A61A9102D174D /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92F5ACA56D636C056F52DDE6 /* Pods_Runner.framework */; };
|
||||
E746073DDE80B82D8D3C9659 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 43C03408D7DF6E3F6C4EC9C7 /* GoogleService-Info.plist */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -47,6 +48,7 @@
|
||||
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
||||
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||
43C03408D7DF6E3F6C4EC9C7 /* GoogleService-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "Runner/GoogleService-Info.plist"; sourceTree = "<group>"; };
|
||||
55914DA7E8E89CB02E73C3F5 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
6BD9A45428DD4E519FC38754 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
@@ -129,6 +131,7 @@
|
||||
331C8082294A63A400263BE5 /* RunnerTests */,
|
||||
2EECBA43D42E2853F949CCFC /* Pods */,
|
||||
9CFCC4FE458D4EC11DAF9E88 /* Frameworks */,
|
||||
43C03408D7DF6E3F6C4EC9C7 /* GoogleService-Info.plist */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@@ -264,6 +267,7 @@
|
||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
|
||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
|
||||
E746073DDE80B82D8D3C9659 /* GoogleService-Info.plist in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>API_KEY</key>
|
||||
<string>AIzaSyAIl2LM2fDrr7OH70K7uJnAwKwXMzPCBMI</string>
|
||||
<key>GCM_SENDER_ID</key>
|
||||
<string>190209969950</string>
|
||||
<key>PLIST_VERSION</key>
|
||||
<string>1</string>
|
||||
<key>BUNDLE_ID</key>
|
||||
<string>com.airkjw.blockseasons</string>
|
||||
<key>PROJECT_ID</key>
|
||||
<string>block-seasons</string>
|
||||
<key>STORAGE_BUCKET</key>
|
||||
<string>block-seasons.firebasestorage.app</string>
|
||||
<key>IS_ADS_ENABLED</key>
|
||||
<false></false>
|
||||
<key>IS_ANALYTICS_ENABLED</key>
|
||||
<false></false>
|
||||
<key>IS_APPINVITE_ENABLED</key>
|
||||
<true></true>
|
||||
<key>IS_GCM_ENABLED</key>
|
||||
<true></true>
|
||||
<key>IS_SIGNIN_ENABLED</key>
|
||||
<true></true>
|
||||
<key>GOOGLE_APP_ID</key>
|
||||
<string>1:190209969950:ios:f9d4578ec86f92c844c30f</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,68 @@
|
||||
// File generated by FlutterFire CLI.
|
||||
// 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 {
|
||||
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: 'AIzaSyCd_3Tw5IxlO6ysp3XMQ9tBsOM1yMYl2MU',
|
||||
appId: '1:190209969950:android:e08dc30877b1821b44c30f',
|
||||
messagingSenderId: '190209969950',
|
||||
projectId: 'block-seasons',
|
||||
storageBucket: 'block-seasons.firebasestorage.app',
|
||||
);
|
||||
|
||||
static const FirebaseOptions ios = FirebaseOptions(
|
||||
apiKey: 'AIzaSyAIl2LM2fDrr7OH70K7uJnAwKwXMzPCBMI',
|
||||
appId: '1:190209969950:ios:f9d4578ec86f92c844c30f',
|
||||
messagingSenderId: '190209969950',
|
||||
projectId: 'block-seasons',
|
||||
storageBucket: 'block-seasons.firebasestorage.app',
|
||||
iosBundleId: 'com.airkjw.blockseasons',
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:firebase_core/firebase_core.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
@@ -8,6 +10,9 @@ import 'app.dart';
|
||||
import 'data/content_repository.dart';
|
||||
import 'data/remote/content_downloader.dart';
|
||||
import 'data/save_repository.dart';
|
||||
import 'firebase_options.dart';
|
||||
import 'services/analytics_service.dart';
|
||||
import 'services/firebase_analytics_backend.dart';
|
||||
import 'state/providers.dart';
|
||||
|
||||
/// Remote content origin. Swap per environment with
|
||||
@@ -22,6 +27,22 @@ Future<void> main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
final saveRepository = await SaveRepository.open();
|
||||
|
||||
// Analytics: real GA4 traffic flows only from release builds so development
|
||||
// never pollutes production. If Firebase init fails (e.g. missing native
|
||||
// config), fall back to the console logger rather than blocking startup.
|
||||
AnalyticsService analytics;
|
||||
try {
|
||||
await Firebase.initializeApp(
|
||||
options: DefaultFirebaseOptions.currentPlatform,
|
||||
);
|
||||
analytics = AnalyticsService(
|
||||
kReleaseMode ? FirebaseAnalyticsBackend() : DebugAnalyticsBackend(),
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrint('Firebase init failed, analytics disabled: $e');
|
||||
analytics = AnalyticsService(DebugAnalyticsBackend());
|
||||
}
|
||||
|
||||
ContentRepository contentRepository;
|
||||
try {
|
||||
final support = await getApplicationSupportDirectory();
|
||||
@@ -42,6 +63,7 @@ Future<void> main() async {
|
||||
overrides: [
|
||||
saveRepositoryProvider.overrideWithValue(saveRepository),
|
||||
contentRepositoryProvider.overrideWithValue(contentRepository),
|
||||
analyticsProvider.overrideWithValue(analytics),
|
||||
],
|
||||
child: const BlockSeasonsApp(),
|
||||
));
|
||||
|
||||
Reference in New Issue
Block a user