Files
BlockSeasons/lib/main.dart
T
airkjw 02021b540e feat(crashlytics): report release crashes to Firebase Crashlytics
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>
2026-06-18 11:17:27 +09:00

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(),
));
}