From 2e6495b4e5ad583cbc3950b5356dcd303741be66 Mon Sep 17 00:00:00 2001 From: Andrea Bizzotto Date: Mon, 18 Mar 2024 16:26:22 +0000 Subject: [PATCH] Use GoRouter redirects to handle app startup state changes (#151) * Use GoRouter redirects to handle app startup state changes * Add dependency override for flutter web https://github.com/firebase/flutterfire/issues/12291#issuecomment-1948455179 * Cleanup GoRouter startup logic * Use NoTransitionPage everywhere, tweak redirect logic --- lib/main.dart | 11 ++-- lib/src/app_startup.dart | 45 +++++++---------- lib/src/routing/app_router.dart | 40 +++++++++++---- pubspec.lock | 89 +++++++++++++++++---------------- pubspec.yaml | 8 +++ 5 files changed, 108 insertions(+), 85 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 76686a5e..9afcf463 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,8 +1,9 @@ +import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:starter_architecture_flutter_firebase/firebase_options.dart'; import 'package:starter_architecture_flutter_firebase/src/app.dart'; -import 'package:starter_architecture_flutter_firebase/src/app_startup.dart'; import 'package:starter_architecture_flutter_firebase/src/localization/string_hardcoded.dart'; // ignore:depend_on_referenced_packages import 'package:flutter_web_plugins/url_strategy.dart'; @@ -14,11 +15,11 @@ Future main() async { // * Register error handlers. For more info, see: // * https://docs.flutter.dev/testing/errors registerErrorHandlers(); + // * Initialize Firebase + await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); // * Entry point of the app - runApp(ProviderScope( - child: AppStartupWidget( - onLoaded: (context) => const MyApp(), - ), + runApp(const ProviderScope( + child: MyApp(), )); } diff --git a/lib/src/app_startup.dart b/lib/src/app_startup.dart index e11bfb3a..4186a687 100644 --- a/lib/src/app_startup.dart +++ b/lib/src/app_startup.dart @@ -1,8 +1,6 @@ -import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:starter_architecture_flutter_firebase/firebase_options.dart'; import 'package:starter_architecture_flutter_firebase/src/constants/app_sizes.dart'; import 'package:starter_architecture_flutter_firebase/src/features/onboarding/data/onboarding_repository.dart'; @@ -20,12 +18,7 @@ Future appStartup(AppStartupRef ref) async { ref.invalidate(onboardingRepositoryProvider); }); // await for all initialization code to be complete before returning - await Future.wait([ - // Firebase init - Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform), - // list of providers to be warmed up - ref.watch(onboardingRepositoryProvider.future) - ]); + await ref.watch(onboardingRepositoryProvider.future); } /// Widget class to manage asynchronous app initialization @@ -52,11 +45,10 @@ class AppStartupLoadingWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return const MaterialApp( - home: Scaffold( - body: Center( - child: CircularProgressIndicator(), - ), + return Scaffold( + appBar: AppBar(), + body: const Center( + child: CircularProgressIndicator(), ), ); } @@ -70,20 +62,19 @@ class AppStartupErrorWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return MaterialApp( - home: Scaffold( - body: Center( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text(message, style: Theme.of(context).textTheme.headlineSmall), - gapH16, - ElevatedButton( - onPressed: onRetry, - child: const Text('Retry'), - ), - ], - ), + return Scaffold( + appBar: AppBar(), + body: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text(message, style: Theme.of(context).textTheme.headlineSmall), + gapH16, + ElevatedButton( + onPressed: onRetry, + child: const Text('Retry'), + ), + ], ), ), ); diff --git a/lib/src/routing/app_router.dart b/lib/src/routing/app_router.dart index 84c8a42a..d6f15899 100644 --- a/lib/src/routing/app_router.dart +++ b/lib/src/routing/app_router.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:starter_architecture_flutter_firebase/src/app_startup.dart'; import 'package:starter_architecture_flutter_firebase/src/features/authentication/data/firebase_auth_repository.dart'; import 'package:starter_architecture_flutter_firebase/src/features/authentication/presentation/custom_profile_screen.dart'; import 'package:starter_architecture_flutter_firebase/src/features/authentication/presentation/custom_sign_in_screen.dart'; @@ -40,16 +41,21 @@ enum AppRoute { } @riverpod -// ignore: unsupported_provider_value GoRouter goRouter(GoRouterRef ref) { + // rebuild GoRouter when app startup state changes + final appStartupState = ref.watch(appStartupProvider); final authRepository = ref.watch(authRepositoryProvider); - final onboardingRepository = - ref.watch(onboardingRepositoryProvider).requireValue; return GoRouter( initialLocation: '/signIn', navigatorKey: _rootNavigatorKey, debugLogDiagnostics: true, redirect: (context, state) { + // If the app is still initializing, show the /startup route + if (appStartupState.isLoading || appStartupState.hasError) { + return '/startup'; + } + final onboardingRepository = + ref.read(onboardingRepositoryProvider).requireValue; final didCompleteOnboarding = onboardingRepository.isOnboardingComplete(); final path = state.uri.path; if (!didCompleteOnboarding) { @@ -61,11 +67,15 @@ GoRouter goRouter(GoRouterRef ref) { } final isLoggedIn = authRepository.currentUser != null; if (isLoggedIn) { - if (path.startsWith('/signIn')) { + if (path.startsWith('/startup') || + path.startsWith('/onboarding') || + path.startsWith('/signIn')) { return '/jobs'; } } else { - if (path.startsWith('/jobs') || + if (path.startsWith('/startup') || + path.startsWith('/onboarding') || + path.startsWith('/jobs') || path.startsWith('/entries') || path.startsWith('/account')) { return '/signIn'; @@ -75,6 +85,16 @@ GoRouter goRouter(GoRouterRef ref) { }, refreshListenable: GoRouterRefreshStream(authRepository.authStateChanges()), routes: [ + GoRoute( + path: '/startup', + pageBuilder: (context, state) => NoTransitionPage( + child: AppStartupWidget( + // * This is just a placeholder + // * The loaded route will be managed by GoRouter on state change + onLoaded: (_) => const SizedBox.shrink(), + ), + ), + ), GoRoute( path: '/onboarding', name: AppRoute.onboarding.name, @@ -92,9 +112,9 @@ GoRouter goRouter(GoRouterRef ref) { // Stateful navigation based on: // https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/stateful_shell_route.dart StatefulShellRoute.indexedStack( - builder: (context, state, navigationShell) { - return ScaffoldWithNestedNavigation(navigationShell: navigationShell); - }, + pageBuilder: (context, state, navigationShell) => NoTransitionPage( + child: ScaffoldWithNestedNavigation(navigationShell: navigationShell), + ), branches: [ StatefulShellBranch( navigatorKey: _jobsNavigatorKey, @@ -202,6 +222,8 @@ GoRouter goRouter(GoRouterRef ref) { ], ), ], - errorBuilder: (context, state) => const NotFoundScreen(), + errorPageBuilder: (context, state) => const NoTransitionPage( + child: NotFoundScreen(), + ), ); } diff --git a/pubspec.lock b/pubspec.lock index 74959d49..d0f6e571 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -13,10 +13,10 @@ packages: dependency: transitive description: name: _flutterfire_internals - sha256: fe4c077084ddda88f327dc1c96d16631cd68d4948644593fcbcd911c2c89e2fa + sha256: "554f148e71e9e016d9c04d4af6b103ca3f74a1ceed7d7307b70a0f41e991eb77" url: "https://pub.dev" source: hosted - version: "1.3.23" + version: "1.3.26" analyzer: dependency: transitive description: @@ -165,26 +165,26 @@ packages: dependency: "direct main" description: name: cloud_firestore - sha256: "6f6a9e7f1c68f34ffe159c911a290fa7caf1a09b8a88aa1534c65f0246953970" + sha256: "621afb8a3752732f865ea2d25e2f5cd01ed16ee5691f5a33958876d0a1cae846" url: "https://pub.dev" source: hosted - version: "4.15.6" + version: "4.15.9" cloud_firestore_platform_interface: dependency: transitive description: name: cloud_firestore_platform_interface - sha256: "4e92549af19f0d2eec7e379ca44f909caef8eb52295a0cde5467b018cfae0378" + sha256: d200d82314548a11a02056ca96861d216ff814a1efe7bcc6595d404202f5212c url: "https://pub.dev" source: hosted - version: "6.1.7" + version: "6.1.10" cloud_firestore_web: dependency: transitive description: name: cloud_firestore_web - sha256: "2b34cff977da11a4822151d967c5e6ce62640cbcb56d6e52a46382d2316350ac" + sha256: "3b3046c23eccc1064328ab010cff4df470b6b1cfd648b82e08a84f5fb210bcb9" url: "https://pub.dev" source: hosted - version: "3.10.6" + version: "3.10.9" code_builder: dependency: transitive description: @@ -253,10 +253,10 @@ packages: dependency: transitive description: name: dart_style - sha256: "25b4624c231844a7a70a3817a729a6190a751ef1c07ded256e126a3b72261444" + sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9" url: "https://pub.dev" source: hosted - version: "2.3.5" + version: "2.3.6" desktop_webview_auth: dependency: transitive description: @@ -309,34 +309,35 @@ packages: dependency: "direct main" description: name: firebase_auth - sha256: "6d8b4455524e2a619a135169a0ae817778d4acf56172188acae85f69f5e67185" + sha256: "3fd6475e60d518c021e70e7d4262db7dac327adf496e71048545da2e0b9ca510" url: "https://pub.dev" source: hosted - version: "4.17.6" + version: "4.17.9" firebase_auth_platform_interface: dependency: transitive description: name: firebase_auth_platform_interface - sha256: "3eed984830f610f43164d539ec6228820cf4936ab6ef491e1afcfaca80143c84" + sha256: d44458576804f246a126fe797547330d2f7bd62069ce12479b583082340a2e4d url: "https://pub.dev" source: hosted - version: "7.1.6" + version: "7.1.9" firebase_auth_web: - dependency: transitive - description: - name: firebase_auth_web - sha256: fcf4718abc722131218de3b84772d83019be48c9211dbd5998aece716c52ff31 - url: "https://pub.dev" - source: hosted - version: "5.9.6" + dependency: "direct overridden" + description: + path: "packages/firebase_auth/firebase_auth_web" + ref: master + resolved-ref: "04280a31310dbbe51a8e619f031f5190d02e695d" + url: "https://github.com/firebase/flutterfire" + source: git + version: "5.10.0" firebase_core: dependency: "direct main" description: name: firebase_core - sha256: "797379ea206eaeeb62499775de812761493d0692890fdc7f90b6183a3369176d" + sha256: "67bf0d5fd78f12f51c6b54a72f6141314136a1a90e98b1b7c45e7fac883254ed" url: "https://pub.dev" source: hosted - version: "2.25.5" + version: "2.27.1" firebase_core_platform_interface: dependency: transitive description: @@ -349,26 +350,26 @@ packages: dependency: transitive description: name: firebase_core_web - sha256: c8e1d59385eee98de63c92f961d2a7062c5d9a65e7f45bdc7f1b0b205aab2492 + sha256: "5377eaac3b9fe8aaf22638d87f92b62784f23572e132dfc029195e84d6cb37de" url: "https://pub.dev" source: hosted - version: "2.11.5" + version: "2.12.0" firebase_dynamic_links: dependency: transitive description: name: firebase_dynamic_links - sha256: "52a482aa2cb571bf504d39a5c0c6ec3fdfbb45526f87558d7fda8101b45c31c0" + sha256: "6f8180d1641ba274e241a690cd12d1163fdec75d807aa9daa37c27489e7ad3f7" url: "https://pub.dev" source: hosted - version: "5.4.15" + version: "5.4.18" firebase_dynamic_links_platform_interface: dependency: transitive description: name: firebase_dynamic_links_platform_interface - sha256: "4ae56fe8b1e43e0b8a693f64673da82b180a7c255bcc221d8f31c6f334e5b9b0" + sha256: "8b6c1827488fa1af56e9a4834ee9e356c510e35d89d1839761064eabad951cbb" url: "https://pub.dev" source: hosted - version: "0.2.6+23" + version: "0.2.6+26" firebase_ui_auth: dependency: "direct main" description: @@ -439,10 +440,10 @@ packages: dependency: "direct main" description: name: flutter_riverpod - sha256: "4bce556b7ecbfea26109638d5237684538d4abc509d253e6c5c4c5733b360098" + sha256: "0f1974eff5bbe774bf1d870e406fc6f29e3d6f1c46bd9c58e7172ff68a785d7d" url: "https://pub.dev" source: hosted - version: "2.4.10" + version: "2.5.1" flutter_svg: dependency: "direct main" description: @@ -513,10 +514,10 @@ packages: dependency: transitive description: name: http - sha256: a2bbf9d017fcced29139daa8ed2bba4ece450ab222871df93ca9eec6f80c34ba + sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.2.1" http_multi_server: dependency: transitive description: @@ -753,10 +754,10 @@ packages: dependency: transitive description: name: riverpod - sha256: "548e2192eb7aeb826eb89387f814edb76594f3363e2c0bb99dd733d795ba3589" + sha256: f21b32ffd26a36555e501b04f4a5dca43ed59e16343f1a30c13632b2351dfa4d url: "https://pub.dev" source: hosted - version: "2.5.0" + version: "2.5.1" riverpod_analyzer_utils: dependency: transitive description: @@ -841,10 +842,10 @@ packages: dependency: transitive description: name: shared_preferences_web - sha256: "7b15ffb9387ea3e237bb7a66b8a23d2147663d391cafc5c8f37b2e7b4bde5d21" + sha256: "9aee1089b36bd2aafe06582b7d7817fd317ef05fc30e6ba14bff247d0933042a" url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.3.0" shared_preferences_windows: dependency: transitive description: @@ -1027,7 +1028,7 @@ packages: source: hosted version: "1.1.0" web: - dependency: transitive + dependency: "direct overridden" description: name: web sha256: "4188706108906f002b3a293509234588823c8c979dc83304e229ff400c996b05" @@ -1038,18 +1039,18 @@ packages: dependency: transitive description: name: web_socket_channel - sha256: "939ab60734a4f8fa95feacb55804fa278de28bdeef38e616dc08e44a84adea23" + sha256: "1d8e795e2a8b3730c41b8a98a2dff2e0fb57ae6f0764a1c46ec5915387d257b2" url: "https://pub.dev" source: hosted - version: "2.4.3" + version: "2.4.4" win32: dependency: transitive description: name: win32 - sha256: "464f5674532865248444b4c3daca12bd9bf2d7c47f759ce2617986e7229494a8" + sha256: "8cb58b45c47dcb42ab3651533626161d6b67a2921917d8d429791f76972b3480" url: "https://pub.dev" source: hosted - version: "5.2.0" + version: "5.3.0" xdg_directories: dependency: transitive description: @@ -1075,5 +1076,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.3.0-279.1.beta <4.0.0" - flutter: ">=3.16.0" + dart: ">=3.3.0 <4.0.0" + flutter: ">=3.19.0" diff --git a/pubspec.yaml b/pubspec.yaml index 1f38c43a..e1c88619 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -41,6 +41,14 @@ dev_dependencies: # import custom_lint too as riverpod_lint depends on it custom_lint: 0.5.6 +dependency_overrides: + web: ^0.4.2 + firebase_auth_web: + git: + url: https://github.com/firebase/flutterfire + ref: master + path: packages/firebase_auth/firebase_auth_web + flutter: uses-material-design: true assets: