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
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
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.
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 theMaterialApp
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:
Then attach the router to the MaterialApp, something like this:
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:
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: