import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import '../state/auth.dart'; import 'routes.dart'; part 'router_notifier.g.dart'; /// This notifier is meant to implement the [Listenable] our [GoRouter] needs. /// /// We aim to trigger redirects whenever's needed. /// This is done by calling our (only) listener everytime we want to notify stuff. /// This allows to centralize global redirecting logic in this class. /// In this simple case, we just listen to auth changes. /// /// SIDE NOTE. /// This might look overcomplicated at a first glance; /// Instead, this method aims to follow some good some good practices: /// 1. It doesn't require us to pipe down any `ref` parameter /// 2. It works as a complete replacement for [ChangeNotifier] (it's a [Listenable] implementation) /// 3. It allows for listening to multiple providers if needed (we do have a [Ref] now!) @riverpod class RouterNotifier extends _$RouterNotifier implements Listenable { VoidCallback? routerListener; bool isAuth = false; // Useful for our global redirect functio @override Future build() async { // One could watch more providers and write logic accordingly isAuth = await ref.watch( authNotifierProvider.selectAsync( (data) => data.map(signedIn: (_) => true, signedOut: (_) => false)), ); ref.listenSelf((_, __) { // One could write more conditional logic for when to call redirection if (state.isLoading) return; routerListener?.call(); }); } /// Redirects the user when our authentication changes String? redirect(BuildContext context, GoRouterState state) { if (this.state.isLoading || this.state.hasError) return null; final isSplash = state.location == const SplashRoute().location; if (isSplash) { return isAuth ? const HomeRoute().location : const LoginRoute().location; } final isLoggingIn = state.location == const LoginRoute().location; if (isLoggingIn) return isAuth ? const HomeRoute().location : null; return isAuth ? null : const SplashRoute().location; } /// Our application routes. Obtained through code generation List get routes => $appRoutes; /// Adds [GoRouter]'s listener as specified by its [Listenable]. /// [GoRouteInformationProvider] uses this method on creation to handle its /// internal [ChangeNotifier]. /// Check out the internal implementation of [GoRouter] and /// [GoRouteInformationProvider] to see this in action. @override void addListener(VoidCallback listener) { routerListener = listener; } /// Removes [GoRouter]'s listener as specified by its [Listenable]. /// [GoRouteInformationProvider] uses this method when disposing, /// so that it removes its callback when destroyed. /// Check out the internal implementation of [GoRouter] and /// [GoRouteInformationProvider] to see this in action. @override void removeListener(VoidCallback listener) { routerListener = null; } }