02021b540e
Routes Flutter framework errors (FlutterError.onError) and uncaught async/platform errors (PlatformDispatcher.onError) to Crashlytics, but only in release builds — debug keeps its red error screens and console traces, and collection is disabled via setCrashlyticsCollectionEnabled (kReleaseMode) so development crashes never reach the dashboard. Adds the Crashlytics Gradle plugin alongside the existing google-services FlutterFire config. iOS dSYM upload run script still to be added in Xcode (symbolication only; crash capture works without it). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
92 lines
3.4 KiB
Dart
92 lines
3.4 KiB
Dart
import 'dart:io';
|
|
|
|
import 'package:firebase_analytics/firebase_analytics.dart';
|
|
import 'package:firebase_core/firebase_core.dart';
|
|
import 'package:firebase_crashlytics/firebase_crashlytics.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';
|
|
|
|
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
|
|
/// --dart-define=CONTENT_BASE_URL=...; the default points at the production
|
|
/// Firebase Hosting site (owner setup pending).
|
|
const contentBaseUrl = String.fromEnvironment(
|
|
'CONTENT_BASE_URL',
|
|
defaultValue: 'https://block-seasons.web.app/content',
|
|
);
|
|
|
|
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,
|
|
);
|
|
// Keep development traffic out of GA4 entirely — this also silences the
|
|
// native SDK's automatic events (session_start, screen_view), not just
|
|
// our custom ones, so only release builds ever reach production analytics.
|
|
await FirebaseAnalytics.instance.setAnalyticsCollectionEnabled(kReleaseMode);
|
|
analytics = AnalyticsService(
|
|
kReleaseMode ? FirebaseAnalyticsBackend() : DebugAnalyticsBackend(),
|
|
);
|
|
|
|
// Crashlytics: collect from release builds only (so debug keeps its red
|
|
// error screens and console traces, and development crashes never reach
|
|
// the production dashboard). In release, route both Flutter framework
|
|
// errors and uncaught async/platform errors to Crashlytics.
|
|
await FirebaseCrashlytics.instance
|
|
.setCrashlyticsCollectionEnabled(kReleaseMode);
|
|
if (kReleaseMode) {
|
|
FlutterError.onError =
|
|
FirebaseCrashlytics.instance.recordFlutterFatalError;
|
|
PlatformDispatcher.instance.onError = (error, stack) {
|
|
FirebaseCrashlytics.instance.recordError(error, stack, fatal: true);
|
|
return true;
|
|
};
|
|
}
|
|
} catch (e) {
|
|
debugPrint('Firebase init failed, analytics disabled: $e');
|
|
analytics = AnalyticsService(DebugAnalyticsBackend());
|
|
}
|
|
|
|
ContentRepository contentRepository;
|
|
try {
|
|
final support = await getApplicationSupportDirectory();
|
|
final cacheDir = Directory('${support.path}/content');
|
|
contentRepository = ContentRepository(
|
|
cacheDir: cacheDir,
|
|
downloader: ContentDownloader(
|
|
baseUrl: contentBaseUrl,
|
|
cacheDir: cacheDir,
|
|
),
|
|
);
|
|
} catch (e) {
|
|
debugPrint('content cache unavailable, bundled only: $e');
|
|
contentRepository = ContentRepository();
|
|
}
|
|
|
|
runApp(ProviderScope(
|
|
overrides: [
|
|
saveRepositoryProvider.overrideWithValue(saveRepository),
|
|
contentRepositoryProvider.overrideWithValue(contentRepository),
|
|
analyticsProvider.overrideWithValue(analytics),
|
|
],
|
|
child: const BlockSeasonsApp(),
|
|
));
|
|
}
|