main function

Future<void> main()

Implementation

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  if (useFirebase) {
    try {
      await Firebase.initializeApp(
        options: DefaultFirebaseOptions.currentPlatform,
      );
      await firebaseService.init();
    } catch (e) {
      log(e.toString(), name: 'Firebase Initialization');
    }
  }

  final container = ProviderContainer();

  // Initialize feature flags early
  await container.read(featureFlagRepositoryProvider).init();

  void showErrorDialog(
    Object error, {
    ErrorType type = ErrorType.unknown,
    StackTrace? stackTrace,
  }) {
    final rootContext = rootNavigatorKey.currentContext;
    if (rootContext == null) return;
    final errorMessage = error.toString();
    final copyText = [
      errorMessage,
      if (stackTrace != null) stackTrace.toString(),
    ].join('\n');
    final errorTitle = switch (type) {
      ErrorType.flutter => t.errors.flutterError,
      ErrorType.async => t.errors.asyncError,
      ErrorType.unknown => t.errors.occurred,
    };

    showDialog(
      context: rootContext,
      builder: (dialogContext) => AlertDialog(
        title: Text(errorTitle),
        // TODO: Remove technical details from user-facing error messages
        content: SelectableText(errorMessage),
        actions: [
          TextButton(
            onPressed: () async {
              await Clipboard.setData(ClipboardData(text: copyText));
              if (!dialogContext.mounted) return;
              Navigator.of(dialogContext).pop();
              if (!rootContext.mounted) return;
              ScaffoldMessenger.maybeOf(rootContext)?.showSnackBar(
                SnackBar(content: Text(t.general.copied)),
              );
            },
            child: Text(t.general.copy),
          ),
          TextButton(
            onPressed: () => Navigator.of(dialogContext).pop(),
            child: Text(t.general.ok),
          ),
        ],
      ),
    );
  }

  // Pass all uncaught "fatal" errors from the framework to Crashlytics
  FlutterError.onError = (details) {
    firebaseService.crashlytics?.recordFlutterFatalError(details);
    FlutterError.dumpErrorToConsole(details);
    WidgetsBinding.instance.addPostFrameCallback((_) {
      showErrorDialog(
        details.exception,
        type: ErrorType.flutter,
        stackTrace: details.stack,
      );
    });
  };

  // Pass all uncaught asynchronous errors that aren't handled by the Flutter framework to Crashlytics
  PlatformDispatcher.instance.onError = (error, stack) {
    firebaseService.crashlytics?.recordError(error, stack, fatal: true);
    showErrorDialog(error, type: ErrorType.async, stackTrace: stack);
    log('Uncaught asynchronous error: $error', stackTrace: stack);
    return true;
  };

  firebaseService.analytics?.logAppOpen();

  await LocaleSettings.useDeviceLocale();

  final authRepository = container.read(authRepositoryProvider);
  final user = await authRepository.getUser();
  if (user != null) container.read(sessionProvider.notifier).create();
  final initialLocation = user != null ? AppRoutes.home : AppRoutes.intro;
  final router = createAppRouter(
    initialLocation: initialLocation,
    container: container,
  );

  runApp(
    UncontrolledProviderScope(
      container: container,
      child: TranslationProvider(
        child: MyApp(router: router),
      ),
    ),
  );
}