Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- void main() async {
- final widgetsBinding = SentryWidgetsFlutterBinding.ensureInitialized();
- await SystemChrome.setPreferredOrientations([
- DeviceOrientation.portraitUp,
- DeviceOrientation.portraitDown,
- ]);
- FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);
- SignalsObserver.instance = null;
- final isOperational = await _initServices();
- final initialDataSourceMode =
- await DataSourceModeController.loadInitialMode();
- final localDatabase = await initializeDatabase();
- runGutZenApp(
- isOperational: isOperational,
- initialDataSourceMode: initialDataSourceMode,
- localDatabase: localDatabase,
- );
- }
- void runGutZenApp({
- required bool isOperational,
- required DataSourceMode initialDataSourceMode,
- AppDatabase? localDatabase,
- }) {
- final startupRoot = _StartupRoot(
- isOperational: isOperational,
- initialDataSourceMode: initialDataSourceMode,
- localDatabase: localDatabase,
- );
- runApp(
- ProviderScope(
- overrides: [
- initialDataSourceModeProvider.overrideWithValue(initialDataSourceMode),
- if (localDatabase != null)
- appDatabaseProvider.overrideWithValue(localDatabase),
- ],
- child: Sentry.isEnabled ? SentryWidget(child: startupRoot) : startupRoot,
- ),
- );
- }
- class _StartupRoot extends StatefulWidget {
- const _StartupRoot({
- required this.isOperational,
- required this.initialDataSourceMode,
- required this.localDatabase,
- });
- final bool isOperational;
- final DataSourceMode initialDataSourceMode;
- final AppDatabase? localDatabase;
- @override
- State<_StartupRoot> createState() => _StartupRootState();
- }
- class _StartupRootState extends State<_StartupRoot> {
- late bool _isOperational;
- AppDatabase? _localDatabase;
- var _isRetrying = false;
- @override
- void initState() {
- super.initState();
- _isOperational = widget.isOperational;
- _localDatabase = widget.localDatabase;
- }
- Future<void> _retry() async {
- setState(() => _isRetrying = true);
- if (!_isOperational) {
- _isOperational = await _initServices();
- }
- final localDatabase = _isOperational ? await initializeDatabase() : null;
- if (!mounted) return;
- if (_isOperational && localDatabase != null) {
- runGutZenApp(
- isOperational: true,
- initialDataSourceMode: widget.initialDataSourceMode,
- localDatabase: localDatabase,
- );
- return;
- }
- setState(() {
- _localDatabase = localDatabase;
- _isRetrying = false;
- });
- }
- @override
- Widget build(BuildContext context) {
- return PostHogWidget(
- child: _isOperational && _localDatabase != null
- ? const MyApp()
- : _ErrorScreen(
- onRetry: _retry,
- isRetrying: _isRetrying,
- ),
- );
- }
- }
- class MyApp extends ConsumerStatefulWidget {
- const MyApp({super.key});
- @override
- ConsumerState<MyApp> createState() => _MyAppState();
- }
- class _MyAppState extends ConsumerState<MyApp> {
- late final AuthStateHandler _authStateHandler;
- late final AppLifecycleListener _listener;
- var _useUpgrader = !Platform.isAndroid; // non-Android always uses upgrader
- var _splashRemoved = false;
- Future<void> _checkAndroidUpdate() async {
- if (!Platform.isAndroid || !kReleaseMode) {
- developer.log("Skipping Android update check", name: 'smart_diet');
- return;
- }
- try {
- final info = await InAppUpdate.checkForUpdate();
- if (!mounted) return;
- if (info.updateAvailability != UpdateAvailability.updateAvailable) {
- return;
- }
- if (!info.flexibleUpdateAllowed) {
- if (!_useUpgrader) {
- setState(() => _useUpgrader = true);
- }
- return;
- }
- final result = await InAppUpdate.startFlexibleUpdate();
- if (!mounted) return;
- if (result == AppUpdateResult.success) {
- _showUpdateReadySnackbar();
- }
- } catch (error, stack) {
- logError(error, stack: stack, context: 'Check Android update failed');
- if (mounted && !_useUpgrader) {
- setState(() => _useUpgrader = true);
- }
- }
- }
- Future<void> _handleUpdateOnResume() async {
- if (!Platform.isAndroid || !kReleaseMode) {
- developer.log(
- "Skipping Android update check on resume",
- name: 'smart_diet',
- );
- return;
- }
- try {
- final info = await InAppUpdate.checkForUpdate();
- if (!mounted) return;
- if (info.installStatus == InstallStatus.downloaded) {
- _showUpdateReadySnackbar();
- return;
- }
- if (info.updateAvailability != UpdateAvailability.updateAvailable) {
- return;
- }
- if (!info.flexibleUpdateAllowed) {
- if (!_useUpgrader) {
- setState(() => _useUpgrader = true);
- }
- return;
- }
- final result = await InAppUpdate.startFlexibleUpdate();
- if (!mounted) return;
- if (result == AppUpdateResult.success) {
- _showUpdateReadySnackbar();
- }
- } catch (error, stack) {
- logError(error, stack: stack, context: 'Check Android update failed');
- if (mounted && !_useUpgrader) {
- setState(() => _useUpgrader = true);
- }
- }
- }
- void _showUpdateReadySnackbar() {
- context.showSnackBar(
- 'An update has been downloaded and is ready to install.',
- persist: true,
- action: SnackBarAction(
- label: 'Restart',
- onPressed: () async {
- await InAppUpdate.completeFlexibleUpdate();
- },
- ),
- );
- }
- @override
- void initState() {
- super.initState();
- unawaited(recordFirstAppOpenIfNeeded());
- _authStateHandler = AuthStateHandler(
- onResetAuthState: () => invalidateAuthScopedProviders(ref),
- onSignOut: () => routerConfig.goNamed(AppRoutes.landing),
- );
- RevenueCatService.setupRevenueCatListener(
- onCustomerInfoUpdate: (customerInfo) async {
- await ref.read(subscriptionProvider.notifier).updateCustomerInfo();
- },
- );
- routerConfig.routerDelegate.addListener(_removeSplashWhenRouterReady);
- WidgetsBinding.instance.addPostFrameCallback(
- (_) => _removeSplashWhenRouterReady(),
- );
- _checkAndroidUpdate();
- _listener = AppLifecycleListener(
- onResume: () {
- _handleUpdateOnResume();
- AccountActivityService.touch();
- },
- );
- // Record activity on launch as well (covers users who rarely background
- // the app between sessions).
- AccountActivityService.touch();
- }
- void _removeSplashWhenRouterReady() {
- if (_splashRemoved || !mounted) return;
- if (routerConfig.routerDelegate.currentConfiguration.isNotEmpty) {
- _splashRemoved = true;
- FlutterNativeSplash.remove();
- routerConfig.routerDelegate.removeListener(_removeSplashWhenRouterReady);
- }
- }
- @override
- void dispose() {
- routerConfig.routerDelegate.removeListener(_removeSplashWhenRouterReady);
- _authStateHandler.dispose();
- _listener.dispose();
- super.dispose();
- }
- @override
- Widget build(BuildContext context) {
- return MaterialApp.router(
- debugShowCheckedModeBanner: false,
- theme: AppTheme.light,
- routerConfig: routerConfig,
- builder: (context, child) => _useUpgrader
- ? UpgradeAlert(
- navigatorKey: routerConfig.routerDelegate.navigatorKey,
- upgrader: Upgrader(
- debugDisplayAlways: kDebugMode,
- debugLogging: kDebugMode,
- ),
- showIgnore: false,
- child: child,
- )
- : child ?? const _ErrorScreen(),
- );
- }
- }
- class _ErrorScreen extends StatelessWidget {
- const _ErrorScreen({this.onRetry, this.isRetrying = false});
- final Future<void> Function()? onRetry;
- final bool isRetrying;
- @override
- Widget build(BuildContext context) {
- FlutterNativeSplash.remove();
- final colorScheme = Theme.of(context).colorScheme;
- return MaterialApp(
- debugShowCheckedModeBanner: false,
- navigatorObservers: [PosthogObserver()],
- theme: AppTheme.light,
- home: Builder(
- builder: (context) => Scaffold(
- body: Padding(
- padding: const EdgeInsets.all(24),
- child: Column(
- mainAxisAlignment: .center,
- crossAxisAlignment: .stretch,
- spacing: 16,
- children: [
- MyCard(
- elevation: .5,
- padding: const EdgeInsets.all(12),
- child: Text(
- appInitializationErrorMessage,
- style: TextStyle(fontWeight: FontWeight.bold),
- textAlign: TextAlign.center,
- ),
- ),
- if (onRetry != null)
- ElevatedButton.icon(
- icon: Icon(Icons.refresh, color: colorScheme.onPrimary),
- label: Text(isRetrying ? 'Retrying…' : 'Try again'),
- onPressed: isRetrying ? null : () => unawaited(onRetry!()),
- ),
- if (isRetrying)
- const Center(child: CircularProgressIndicator()),
- ElevatedButton.icon(
- icon: Icon(Icons.error, color: colorScheme.onPrimary),
- label: const Text('Contact Support'),
- onPressed: () async {
- Sentry.isEnabled
- ? await Navigator.push<void>(
- context,
- MaterialPageRoute<void>(
- fullscreenDialog: true,
- builder: (context) => SentryFeedbackForm(
- associatedEventId: Sentry.lastEventId,
- ),
- ),
- )
- : await sendEmail(
- supportEmail,
- subject: 'App startup issue',
- body:
- 'Please describe what you see on screen, the error message, and the actions you took before the error occurred:\n\n',
- );
- },
- ),
- ],
- ),
- ),
- ),
- ),
- );
- }
- }
- Future<bool> _initServices() async {
- // No appRunner: non-web uses OnErrorIntegration (PlatformDispatcher.onError).
- await SentryFlutter.init(
- (options) {
- options.dsn = Env.sentryDsn;
- // Re-tune sampling as traffic/quotas change; see docs/pre-deployment.md.
- options.tracesSampleRate = !kReleaseMode ? 0.0 : 1.0;
- // Disable user interaction tracking to avoid notch geometry errors
- options.enableUserInteractionTracing = false;
- options.enableUserInteractionBreadcrumbs = false;
- },
- ).onError((Object error, StackTrace stack) async {
- await Sentry.close();
- developer.log(
- 'SentryFlutter.init failed; continuing without Sentry.',
- name: 'smart_diet',
- error: error,
- stackTrace: stack,
- );
- });
- if (!await SupabaseServices.init()) {
- return false;
- }
- await RevenueCatService.initPlatformState();
- await AppsFlyerService.init();
- await PosthogService.init();
- return true;
- }
Advertisement
Add Comment
Please, Sign In to add comment