skip to Main Content

currently, I have as an example a cubit that is injected with BlocProvider in ScreenA.

class ScreenA extends StatelessWidget {
  const ScreenA({super.key});

  @override
  Widget build(BuildContext context) {
    return BlocProvider<MyCubit>(
      create: (context) => MyCubit(),
      child: ...
    );
  }
}

For the widget sub-tree the MyCubit cubit will be available to use, right?

Now, If I have another route ScreenB that I want to pass that cubit to, it will be available as well in that route:

class ScreenA extends StatelessWidget {
  const ScreenA({super.key});

  @override
  Widget build(BuildContext context) {
    return BlocProvider<MyCubit>(
      create: (context) => MyCubit(),
      child: ElevatedButton(
        onPressed: () {
          Navigator.of(context).pushNamed("/screenB");
        },
        child: Text('Screen A'),
      ),
    );
  }
}

as you can see, I can’t pass that cubit to that ScreenB via constructor since we have a named route, and so in order to pass it I use currently the arguments property in the Navigator‘s pushNamed method like this:

Navigator.of(context).pushNamed("/screenB", arguments: context.read<MyCubit>());

and in the ScreenB, I read it like this:

class ScreenB extends StatelessWidget {
  const ScreenB({super.key});

  @override
  Widget build(BuildContext context) {
    final cubit = ModalRoute.of(context)!.settings.arguments as MyCubit;
    return BlocProvider<MyCubit>.value(
      value: cubit,
      child: Container(),
    );
  }
}

but as you can see, now I did list the Dart’s types system and I need now to cast the ModalRoute.of(context)!.settings.arguments to my cubit, in addition, if I will need to change something in ModalRoute.of(context)!.settings.arguments, I will have now to reflect changes in other places, and this always ends up wasting my time and be error-prone.

My question is:

What is the better option that I can do to pass a cubit to another named route without needing to cast its type or to relate it to other arguments so it will not be error-prone for me ?

Note: I don’t talk about global blocs/cubits, I need to create a cubit/bloc in a specific nested route and want it to be available in others as well.

Thank you!

2

Answers


  1. You could use nested routes. Essentially, ScreenA and ScreenB can have the same ancestor, and your Bloc can be injected on the Wrapper route.

    Basically something like this

    BlocProvider<MyCubit>
      NestedWrapperPage
        Navigator
          - ScreenA
          - ScreenB
    

    The flutter docs might help you with setting this up.

    As an alternative to the official docs (and I highly recommend), consider using AutoRoute to manage your navigation logic. Setting up nested routes would be a breeze with this.

    Login or Signup to reply.
  2. I don’t think it is easily done in any other way than with a package such as AutoRoute if you want to create the blocs on ScreenA and cannot/don’t want to create them when defining your named routes in the MaterialApp or e.g. onGenerateRoute.

    That is obviously done with blocs defined when creating the MaterialApp. They are not global in the common sense as they can absolutely be made private to the MaterialWidget, and only passed to the correct routes.

    So something like this:

    class AppRouter {
      final MyCubit _cubit = MyCubit();
    
      Route onGenerateRoute(RouteSettings routeSettings) {
        switch (routeSettings.name) {
          case '/screenA':
            return MaterialPageRoute(
              builder: (context) => BlocProvider.value(
                value: _cubit,
                child: ScreenA(),
              ),
            );
          case '/screenB':
            return MaterialPageRoute(
              builder: (context) => BlocProvider.value(
                value: _cubit,
                child: ScreenB(),
              ),
            );
        }
      }
    }
    

    Then attach the router to the MaterialApp, something like this:

    final _appRouter = AppRouter();
    MaterialApp(
      title: 'Title',
      onGenerateRoute: _appRouter.onGenerateRoute,
    )
    

    But (maybe you already know) you can provide the bloc using BlocProvider.value() wrapping the MaterialRoute with non-named routing when you are on ScreenA:

    Navigator.of(context).push(
      MaterialPageRoute(
        builder: (context) => BlocProvider.value(
          value: context.read<MyCubit>(),
          child: ScreenB(),
        ),
      ),
    );
    

    Or using e.g. AutoRoute in a bunch of different ways. Such as Nested Routing as already explained in a different answer, or you can pass the bloc as a type safe parameter to the generated route for ScreenB. Something like this:

    AutoRouter.of(context).push(ScreenBRoute(bloc: context.read<MyCubit>()))
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search