Skip to content

Commit

Permalink
Use GoRouter redirects to handle app startup state changes (#151)
Browse files Browse the repository at this point in the history
* Use GoRouter redirects to handle app startup state changes

* Add dependency override for flutter web

firebase/flutterfire#12291 (comment)

* Cleanup GoRouter startup logic

* Use NoTransitionPage everywhere, tweak redirect logic
  • Loading branch information
bizz84 authored Mar 18, 2024
1 parent a756a90 commit 2e6495b
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 85 deletions.
11 changes: 6 additions & 5 deletions lib/main.dart
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -14,11 +15,11 @@ Future<void> 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(),
));
}

Expand Down
45 changes: 18 additions & 27 deletions lib/src/app_startup.dart
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -20,12 +18,7 @@ Future<void> 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
Expand All @@ -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(),
),
);
}
Expand All @@ -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'),
),
],
),
),
);
Expand Down
40 changes: 31 additions & 9 deletions lib/src/routing/app_router.dart
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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) {
Expand All @@ -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';
Expand All @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -202,6 +222,8 @@ GoRouter goRouter(GoRouterRef ref) {
],
),
],
errorBuilder: (context, state) => const NotFoundScreen(),
errorPageBuilder: (context, state) => const NoTransitionPage(
child: NotFoundScreen(),
),
);
}
Loading

0 comments on commit 2e6495b

Please sign in to comment.