I’ve been struggling with the exact same question and have researched several alternatives and options.
TL;DR: Option 3 is my preferred choice, which uses the GoRouter.refresh()
method at the main()
level to dynamically update the GoRouter state based on events from the auth stream.
Option 1: Follow the go_router async_redirection.dart example
See here for the example: https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/async_redirection.dart
This wraps the top level app, typically MyApp
(as returned by main()
in runApp()
) in an InheritedNotifer widget, which they call StreamAuthScope
and which creates a dependency between the notifier StreamAuthNotifier
and go_router's parsing pipeline. This in turn will rebuild MyApp
(or App
in the example) when the auth status changes (as communicated by StreamAuthNotifier
via notifyListeners()
).
I implemented a similar model based on the Provider package where the ChangeProviderNotifier
replaces StreamAuthScope
and wraps the top level MyApp
returned by main()
. However this doesn’t allow the creation of a monitored Provider.of<>
inside the GoRouter( redirect: )
enclosure. To solve this I created a getRouter
function that passed in isUserSignIn
which was monitored with a Provider.of<>
in the main body of MyApp
but before the build
function. This works but feels cumbersome and
causes the main MyApp
to be rebuilt each time auth status changes. If desired, I’m sure you could do something similar with a BLoC model in place of Provider.
Option 2: Use GoRouter
’s refreshListenable:
parameter
This is based on this go_router redirection.dart example: https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/redirection.dart
In the question you mentioned you have a stream that notifies auth state changes. You can wrap this in a class with extends ChangeNotifier
to make it Listenable. Then in the constructor you can instantiate and monitor the stream with .listen
, and in the enclosure issue a notifyListerners()
each time there is an auth state change (probably each time there is a stream event). In my case I called this class AuthNotifier
This can then be used as the listenable with GoRouter
’s refreshListenable:
parameter simply as: refreshListenable: AuthNotifier()
Example AuthNotifier
class
class AuthNotifier extends ChangeNotifier {
AuthNotifier() {
// Continuously monitor for authStateChanges
// per: https://firebase.google.com/docs/auth/flutter/start#authstatechanges
_subscription =
FirebaseAuth.instance.authStateChanges().listen((User? user) {
// if user != null there is a user logged in, however
// we can just monitor for auth state change and notify
notifyListeners();
});
} // End AuthNotifier constructor
late final StreamSubscription<dynamic> _subscription;
@override
void dispose() {
_subscription.cancel();
super.dispose();
}
}
Note: to avoid multiple streams being created and monitored, you need to ensure this constructor is only called once in your app (in this case as part of GoRouter
’s refreshListenable:
), or else modify it to be a singleton.
Option 3: Use GoRouter
’s .refresh()
method
A similar, but more direct approach to option 2 is to use GoRouter
’s .refresh()
method. This directly calls an internal notifyListerners()
that refreshes the GoRouter configuration. We can use a similar class to the AuthNotifier
above but we don’t need extends ChangeNotifier
and would call router.refresh()
in place of notifyListeners()
, where router
is your GoRouter()
configuration. This new class would be instantiated in main()
.
Given its so simple (2-3 lines of code), we can also skip the class definition and instantiation and implement the functionality directly in the main()
body, as follows:
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
// Listen for Auth changes and .refresh the GoRouter [router]
FirebaseAuth.instance.authStateChanges().listen((User? user) {
router.refresh();
});
runApp(
const MyApp(),
);
}
Since this appears to be the most direct and simplest solution, it is my preferred solution and the one I have implemented. However there is a lot of confusing and dated information out there and I don’t feel I have enough experience to claim it as any sort of 'best practice', so will leave that for others to judge and comment.
I hope all this helps you and others as it’s taken me a long time with work out these various options and wade through the wide range of materials and options out there. I feel there is a definitely an opportunity to improve the official go_router documentation in this area !