main function
Implementation
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
LicenseRegistry.addLicense(() async* {
final license = await rootBundle.loadString('assets/fonts/OFL.txt');
yield LicenseEntryWithLineBreaks(['Noto Sans TC'], license);
});
// TODO: Remove orientation restriction after responsive layouts are complete.
await SystemChrome.setPreferredOrientations([
.portraitUp,
]);
if (useFirebase) {
try {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
} catch (e) {
log(e.toString(), name: 'Firebase Initialization');
}
}
final container = ProviderContainer();
void showErrorDialog(
Object error, {
ErrorType type = .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) {
.flutter => t.errors.flutterError.spaced,
.async => t.errors.asyncError,
.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();
rootScaffoldMessengerKey.currentState?.showSnackBar(
SnackBar(content: Text(t.general.copied)),
);
},
child: Text(t.general.copy),
),
TextButton(
onPressed: () => Navigator.of(dialogContext).pop(),
child: Text(t.general.ok),
),
],
),
);
}
void showErrorSnackBar(Object error) {
final message = isNetworkError(error)
? t.errors.networkError
: t.errors.unexpected;
rootScaffoldMessengerKey.currentState
?..hideCurrentSnackBar()
..showSnackBar(
SnackBar(
content: Text(message),
),
);
}
Future<bool> shouldShowErrorDialog() async {
try {
return await container
.read(preferencesRepositoryProvider)
.get(.showErrorDialog);
} catch (e) {
log('Failed to resolve error display preference: $e');
return PrefKey.showErrorDialog.defaultValue;
}
}
Future<void> handleUncaughtError(
Object error, {
ErrorType type = .unknown,
StackTrace? stackTrace,
}) async {
final showDialog = await shouldShowErrorDialog();
if (showDialog) {
showErrorDialog(error, type: type, stackTrace: stackTrace);
} else {
showErrorSnackBar(error);
}
}
// Pass all uncaught "fatal" errors from the framework to Crashlytics
FlutterError.onError = (details) {
firebaseService.crashlytics?.recordFlutterFatalError(details);
FlutterError.dumpErrorToConsole(details);
WidgetsBinding.instance.addPostFrameCallback((_) {
handleUncaughtError(
details.exception,
type: .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);
log('Uncaught asynchronous error: $error', stackTrace: stack);
handleUncaughtError(error, type: .async, stackTrace: stack);
return true;
};
firebaseService.analytics?.logAppOpen();
await LocaleSettings.useDeviceLocale();
// Initialize Remote Config and preference defaults
await container.read(preferencesRepositoryProvider).init();
final database = container.read(databaseProvider);
final user = await database.select(database.users).getSingleOrNull();
if (user != null) {
// Restore demo mode if the stored user is the demo account
if (user.studentId == demoUsername) {
container.read(isDemoProvider.notifier).set(true);
}
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),
),
),
);
}