Home > Software engineering >  Flutter read riverpod provider in GoRoute builder
Flutter read riverpod provider in GoRoute builder

Time:10-07

My Flutter project is migrating to go_router and I have trouble understanding how to access riverpod providers in either a GoRoute's build method or redirect method, as I need to access the user's data to control the navigation.

I have a top-level redirect that checks if a user is logged in and sends them to a LoginPage if not. All users can access so-called activities in my app, but only admins can edit them. Whether a user is an admin is stored in a riverpod userDataProvider, which always contains a user if the user is logged in. Now if a user attempts to enter the route /:activityId?edit=true, I want to check whether they are allowed to by accessing the userDataProvider. However, I do not see what the clean way of accessing this provider is.

I found somewhere (can't find the thread anymore), that one way is to use ProviderScope.containerOf(context).read(userDataProvider), but I have never seen this before and it seems a bit exotic to me. Is this the way to go?

My GoRoute looks something like this:

GoRoute(
  path: RouteName.event.relPath,
  builder: (context, state) {
    final String? id = state.params['id'];
    final bool edit = state.queryParams['edit'] == 'true';

    if (state.extra == null) {
      // TODO: Fetch data
    }

    final data = state.extra! as Pair<ActivityData, CachedNetworkImage?>;

    if (edit) {
      return CreateActivity(
        isEdit: true,
        data: data.a,
        banner: data.b,
      );
    }

    return ActivityPage(
      id: id!,
      data: data.a,
      banner: data.b,
    );
  },
  redirect: (context, state) {
    final bool edit = state.queryParams['edit'] == 'true';
    if (edit) {
      // IMPORTANT: How to access the ref here?
      final bool isAdmin =
          ref.read(userDataProvider).currentUser.customClaims.admin;
      if (isAdmin) {
        return state.location; // Includes the queryParam edit
      } else {
        return state.subloc; // Does not include queryParam
      }
    } else {
      return state.path;
    }
  },
),

CodePudding user response:

In my current application, I used something similar approach like this :

Provider registration part (providers.dart) :

    final routerProvider = Provider<GoRouter>((ref) {
      final router = RouterNotifier(ref);
      return GoRouter(
          debugLogDiagnostics: true,
          refreshListenable: router,
          redirect: (context, state) {
            router._redirectLogic(state);
            return null;
          },
          routes: ref.read(routesProvider));
    });
    
    class RouterNotifier extends ChangeNotifier {
      final Ref _ref;
    
      RouterNotifier(this._ref) {
        _ref.listen<AuthState>(authNotifierProvider, (_, __) => notifyListeners());
      }
    
      String? _redirectLogic(GoRouterState state) {
        final loginState = _ref.watch(authNotifierProvider);
    
        final areWeLoggingIn = state.location == '/login';
    
        if (loginState.state != AuthenticationState.authenticated) {
          return areWeLoggingIn ? null : '/login';
        }
    
        if (areWeLoggingIn) return '/welcome';
    
        return null;
      }
    }

Main app building as router (app.dart):

    class App extends ConsumerWidget {
      const App({Key? key}) : super(key: key);
    
      // This widget is the root of your application.
      @override
      Widget build(BuildContext context, WidgetRef ref) {
        final GoRouter router = ref.watch(routerProvider);
        return MaterialApp.router(
          routeInformationProvider: router.routeInformationProvider,
          routeInformationParser: router.routeInformationParser,
          routerDelegate: router.routerDelegate,
          debugShowCheckedModeBanner: false,
          title: 'Flutter Auth',
        }
      }
    }

And as entrypoint (main.dart):

    Future<void> main() async {
      F.appFlavor = Flavor.dev;
      WidgetsFlutterBinding.ensureInitialized();
      await setup();
      runApp(ProviderScope(
        observers: [
          Observers(),
        ],
        child: const App(),
      ));
    } 

CodePudding user response:

for you to be able to access the ref property, you need to assign your go router to a provider like this:

final goRouterProvider = Provider<GoROuter>((ref)){
return GoRouter(
initialLocation: '/',
redirect: (state){

//you can now use ref here or anywhere inside this provider

final user = ref.watch(authRepository).currentUser;
}
routes:[
GoRouter(
path: 'login',
name: 'login',
builder: (context, state) => LoginScreen()
),

GoRouter(
path: 'home',
name: 'home',
builder: (context, state) => HomeScreen()
),
]
);
}

then you can watch and use your go_router in your material app like so:

  @override
  Widget build(BuildContext context, WidgetRef ref) {
       final goRouter = ref.watch(goRouterProvider);
    return MaterialApp.router(
        routeInformationProvider: goRouter.routeInformationProvider,
        routerDelegate: goRouter.routerDelegate,
        routeInformationParser: goRouter.routeInformationParser,
        debugShowCheckedModeBanner: false,
    );
  }
}

hope this helps

  • Related