How to scope the BlocProvider correctly when using go_router type-safe routes?

359 Views Asked by At

I'm using a shell route setup in Flutter with go_router where I have a HomePage and a ProfilePage. From HomePage, I navigate to DetailsPage using a button. My intention is to access HomeCubit in DetailsPage to perform a specific task. However, when I try to use context.read<HomeCubit>().task() in DetailsPage, I encounter a ProviderNotFoundException.

The exact error message is:

Exception has occurred. ProviderNotFoundException (Error: Could not find the correct Provider above this DownloadParametersPage Widget

This happens because you used a BuildContext that does not include the provider of your choice. There are a few common scenarios:

You added a new provider in your main.dart and performed a hot-reload. To fix, perform a hot-restart.

The provider you are trying to read is in a different route.

Providers are "scoped". So if you insert of provider inside a route, then other routes will not be able to access that provider.

You used a BuildContext that is an ancestor of the provider you are trying to read.

Make sure that DownloadParametersPage is under your MultiProvider/Provider. This usually happens when you are creating a provider and trying to read it immediately.

The error suggests that the BuildContext in DetailsPage does not include HomeCubit. I understand that providers in Flutter are scoped, and if a provider is inserted in one route, it might not be accessible in another. However, I'm not sure how to structure my code to make HomeCubit available in DetailsPage.

Here is the relevant part of my implementation using type-safe routes with go_router:

// Shell route with HomeRoute and ProfileRoute
@TypedShellRoute<MyShellRouteData>(
  routes: <TypedRoute<RouteData>>[
    TypedGoRoute<HomeRoute>(path: '/home', routes: [
      TypedGoRoute<DetailsRoute>(path: 'details'),
    ]),
    TypedGoRoute<ProfileRoute>(path: '/profile'),
  ],
)
class MyShellRouteData extends ShellRouteData {
  // Implementation details...
}

// HomeRoute with BlocProvider for HomeCubit
@immutable
class HomeRoute extends GoRouteData {
  @override
  CustomTransitionPage<void> buildPage(BuildContext context, GoRouterState state) {
    return CustomTransitionPage<void>(
      key: state.pageKey,
      child: BlocProvider<DiagnosesCubit>(
        create: (context) => HomeCubit(),
        child: const HomePage(),
      ),
      // Transition details...
    );
  }
}

// DetailsRoute implementation
@immutable
class DetailsRoute extends GoRouteData {
  @override
  Widget build(BuildContext context, GoRouterState state) {
    return const DetailsPage();
  }
}

// ProfileRoute implementation
@immutable
class ProfileRoute extends GoRouteData {
  // Implementation details...
}

I tried to read the go_router documentation but I didn't find any useful tips regarding this issue.

How can I structure my routes or modify my provider setup so that HomeCubit is accessible in DetailsPage?

Thank you in advance!

1

There are 1 best solutions below

0
On

Hope I'm not late for this conversation!

In GoRouter, when you nest a route in another GoRouter.routes, the route is not nested the way we would think. The widgets are instantiate in a parallel way. Which means that their context are never nested. You can open devtools and inspect the widget tree and you will see this.

In order for DetailsPage to access your HomeCubit. You need to have a shared context in common. This can only be done by using ShellRoute.

[
 ShellRoute(
  builder: (_, __, child) {
    //any provider you put here, will be accessible by any descendant route, effectively creating a scope.
    return BlocProvider(..., child: child);
  }
  routes: [
    DetailsRoute(), // can access Cubit
    ProfileRoute(), // can access Cubit
  ]
 ),
 ...
]

I made a small package solving the issue of nested routes, so it works more fluently and without the need of this boilerplate. Take a look: https://pub.dev/packages/go_provider