Im using Flutter and flutter_bloc for a small app, and i used MultiBlocProvider to use multiple BlocProviders that i need in the main home page, and under the home page, there is a MainWidget, which can access the given Bloc easily by: BlocProvider.of<OldBloc>(context)
The MainWidget calls NewWidget as a dialog by: showDialog(context: context, builder: (context) => NewWidget())
The problem is, i cannot access OldBloc from NewWidget(), so i assumed that MainWidget isnt passing its context to NewWidget when using showDialog ?
HomeScreen.dart
import 'package:flutter/material.dart';
class HomeScreen extends StatelessWidget {
const HomeScreen({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: MultiBlocProvider(
providers: [
BlocProvider(
create: (context) => OldBloc()..add(Initialize()),
),
BlocProvider(
create: (context) => OtherBloc()..add(Initialize()),
),
],
child: Stack(
children: [
MainWidget(),
MenuWidget(),
],
),
));
}
}
MainWidget.dart
import 'package:flutter/material.dart';
class MainWidget extends StatelessWidget {
const MainWidget({
Key key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return TextField(onTap: () => showDialog(context: context, builder: (context) => NewWidget()));
}
}
NewWidget.dart
import 'package:flutter/material.dart';
class NewWidget extends StatelessWidget {
const NewWidget({Key key}) : super(key: key);
#override
Widget build(context) {
return Text(BlocProvider.of<OldBloc>(context).name); // <------- THIS GIVES AN ERROR THAT IT CANT FIND A BLOC OF THE TYPE OldBloc
}
}
You can simply use this (suggested by Flex Angelov):
showDialog(
context: superContext,
builder: (_) {
return BlocProvider.value(
value: superContext.read<MyBloc>(),
child: const MyDialogWidget(),
);
},
);
and make sure superContext has access to your BLoC.
You have no access to BuildContext in your showDialog method, documentation:
The widget returned by the builder does not share a context with the location that showDialog is originally called from.
The context argument is used to look up the Navigator and Theme for the dialog. It is only used when the method is called.
Its also recommended to use a StatefulBuilder or a custom StatefulWidget, here is an example.
I solved this problem.
I use cubit instead of bloc but it doesn't matter.
ShowDialog doesn't pass parent context. So you should create a new cubit and pass your parent cubit and state in arguments for a new cubit. I called main cubit FooCubit, new cubit FooCubitWrapper and DialogWidget is child widget that needs some BLOC logic in showDialog.
var fooCubit = context.read<FooCubit>(); // your parrent cubit and state
return BlocBuilder<FooCubit , FooState>(
builder: (_, state) {
.
.///some logic///
.
showDialog(
context: context,
builder: (_) => // here you have to create new cubit
BlocProvider( // because there is no access to parent cubit
create: (_) => FooCubitWrapper(cubit, state),
child: DialogWidget(),
),
);
This is FooCubitWrapper. For example, we need the boo method from the parent cubit. So we need to create here boo method and inside we need to reference to parent boo method and emit parent state.
class FooCubitWrapper extends Cubit<FooState> {
FooCubit fooCubit;
FooCubitWrapper(this.fooCubit, FooState initialState) : super(initialState);
boo() {
fooCubit.boo();
emit(fooCubit.state);
}
}
And finally, in your DialogWidget, you do all like usual
var cubit = context.read<TagsCubitWrapper>();
return BlocBuilder<TagsCubitWrapper, TagsState>(
builder: (context, state) {
// work with methods and fields as usual
cubit.boo();
if !(state.someField) {}
});
Related
I trying to using the auto_route and flutter_bloc libraries to navigate page, but BlocListener is not triggered.
I'm using print(SplashRoute == NavigationState.initial().routeType); to check the trigger condition with BlocListener, it's return true.
However, the BlocListener still not triggered.
How do I fix my code problem :(? This is the sample code of my app. Thanks.
main.dart
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(const AppWidget());
}
class AppWidget extends StatelessWidget {
const AppWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final rootRouter = RootRouter();
return MultiBlocProvider(
providers: [
BlocProvider(
create: (_) => NavigationCubit()..nav(SplashRoute),
),
// ... Other blocProvider
],
child: BlocBuilder<ThemeCubit, ThemeState>(
builder: (context, state) {
return MaterialApp.router(
debugShowCheckedModeBanner: false,
theme: state.themeData,
routerDelegate: rootRouter.delegate(),
routeInformationParser: rootRouter.defaultRouteParser(),
);
},
));
}
}
class SplashPage extends StatelessWidget {
const SplashPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
print(SplashRoute == NavigationState.initial().routeType); // <------ return ture
return MultiBlocListener(
listeners: [
BlocListener<NavigationCubit, NavigationState>( // <------ Not working here
listenWhen: (p, c) => c.routeType is SplashRoute,
listener: (context, state) {
LoggerService.simple.i('NavigationCubit page listening!!');
context.read<NavigationCubit>().nav(HomeRoute);
context.pushRoute(const HomeRoute());
},
// ... Other blocListener
),
],
child: const Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
),
);
}
}
navigation_state.dart
part of 'navigation_cubit.dart';
#freezed
abstract class NavigationState with _$NavigationState {
const factory NavigationState({
required Type routeType,
}) = _NavigationState;
factory NavigationState.initial() => const NavigationState(
routeType: SplashRoute,
);
}
navigation_cubit.dart
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import '../../../presentation/routes/router.gr.dart';
part 'navigation_cubit.freezed.dart';
part 'navigation_state.dart';
class NavigationCubit extends Cubit<NavigationState> {
NavigationCubit() : super(NavigationState.initial());
void nav(Type routeType) {
emit(
state.copyWith(
routeType: routeType,
),
);
}
#override
Future<void> close() async {
return super.close();
}
}
There are two things I can interpret from your code. Either way it will not work as you had hoped.
My understanding from your question is that you are trying to navigate using the BlocListener on initial state, which doesn't work.
The reason is that BlocListener is not triggered on initial state as it is not a state change, but rather something that is defined by the bloc.
The second thing I see is that you call the nav method when providing the bloc, which is a good thing: NavigationCubit()..nav(SplashRoute). However, it will set the same value for the parameter routeType, which will not trigger a state change as it is the same value. Meaning that the BlocListener will not be triggered.
Set routeType to something else initially, perhaps set it to null, so that your bloc can identify a state change, then your BlocListener will be triggered.
EDIT:
Also, c.routeType is SplashRoute doesn't seem right. try changing to c.routeType == SplashRoute in your listenWhen property, otherwise your function in the listener property will not trigger
I am trying Flutter for the first time, and I am a little confused by the MultiProvider class.
The question is straightforward, but I didn't find an explanation:
when should one use Consumer and when context.watch?
For instance, taking one of the examples apps I have found, I tried using two providers for two global states, the theme and the status of the app:
runApp(
MultiProvider(providers: [
ChangeNotifierProvider(create: (context) => AppTheme()),
ChangeNotifierProvider(create: (context) => AppStatus()),
],
child: const MyApp()
));
Then the app widget accesses the theme with Consumer:
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Consumer<AppTheme>(
builder: (context, appTheme, child) {
// ...
As far as I understand, now all children widgets will inherit the provider. Is it right?
My home page, then, called by the MyApp class does not use Consumer, but context.watch:
#override
Widget build(BuildContext context) {
final appTheme = context.watch<AppTheme>();
final appStatus = context.watch<AppStatus>();
return NavigationView(
// ...
It works, don't get me wrong, but I just copied the row above my appStatus, so I don't really fully understand it. This is also due to another screen that I've concocted to access the AppStatus global state, but I use Consumer, as suggested by the Flutter documentation:
class _ViewerState extends State<Viewer> {
#override
Widget build(BuildContext context) {
return Consumer<AppStatus>(
builder: (context, appStatus, child) {
return ScaffoldPage.scrollable(
header: const PageHeader(title: Text('Test')),
children: [
FilledButton(child: Text("Try ${appStatus.count}"), onPressed: (){ appStatus.increment(); debugPrint('pressed ${appStatus.count}'); }),
FilledButton(child: Text("Reset"), onPressed: (){ appStatus.reset(); }),
]);
},
);
}
}
I have the feeling that I am misusing something here, and I do not really understand what's going on under the hood...
context.watch<T>() and Consumer<T> does the same thing. Most of the time context.watch<T>() is just more convenient. In some cases where context is not available Consumer<T> is useful.
I can't access a provider defined above a Scaffold from showModalBottomSheet in the FloatingActionButton.
I've defined a HomePage like so:
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => MyProvider(),
builder: (context, _) {
return Scaffold(
body: Consumer<MyProvider>(
builder: (context, provider, _) {
return Text(provider.mytext); // this works fine
}
),
floatingActionButton: MyFAB(), // here is the problem
);
}
)
}
}
And this is MyFAB:
class MyFAB extends StatefulWidget {
#override
_MyFABState createState() => _MyFABState();
}
class _MyFABState extends State<MyFAB> {
#override
Widget build(BuildContext context) {
return FloatingActionButton(
...
onPressed: () => show(),
);
}
void show() {
showModalBottomSheet(
...
context: context,
builder: (BuildContext context) {
return Wrap(
children: [
...
FlatButton(
onPressed: Provider.of<MyProvider>(context, listen: false).doSomething(); //Can't do this
Navigator.pop(context);
)
],
);
}
);
}
}
Error: Could not find the correct Provider<MyProvider above this BottomSheet Widget.
Fixed by placing the provider above MaterialApp, as described here.
Bottom sheets are created at the root of the material app. If a prodiver is declared below the material app, a bottom sheet cannot access it because the provider is not an ancestor of the bottom sheet in the widget tree.
The screenshot below shows a widget tree: the whole app is inside Wrapper and the bottom sheet is not created inside Wrapper. It is created as another child of MaterialApp (with a root element Container in this case).
For your case:
// main.dart
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => MyProvider(),
builder: (context, _) {
return MaterialApp(
home: HomePage(),
);
},
);
}
}
// home_page.dart
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: MyFAB()
);
}
}
This is caused by passing it the wrong context. Wrap your FAB to a Builder widget and pass it as builder property. This will take a new context and pass it to showModalBottomSheet. Also, you can do onPressed: show, it's more concise.
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => MyProvider(),
builder: (context, _) {
return Scaffold(
body: Consumer<MyProvider>(
builder: (context, provider, _) {
return Text(provider.mytext); // this works fine
}
),
floatingActionButton: MyFAB(context), // here is the problem
);
}
)
}
}
class MyFAB extends StatefulWidget {
#override
_MyFABState createState() => _MyFABState();
}
class _MyFABState extends State<MyFAB> {
#override
Widget build(BuildContext context) {
return FloatingActionButton(
...
onPressed: (context) => show(context),
);
}
void show(ctx) {
showModalBottomSheet(
...
context: ctx,
builder: (BuildContext context) {
return Wrap(
children: [
...
FlatButton(
onPressed: () {
Provider.of<MyProvider>(ctx, listen: false).doSomething(); //Can't do this
Navigator.pop(ctx)
};
)
],
);
}
);
}
}
SOLUTION
HomePage:
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => MyProvider(),
builder: (context, _) {
return Scaffold(
body: Consumer<MyProvider>(
builder: (context, provider, _) {
return Text(provider.mytext); // this works fine
}
),
floatingActionButton: MyFAB(context), // here is the problem
);
}
)
}
}
MyFAB:
class MyFAB extends StatefulWidget {
final BuildContext ctx;
MyFAB(this.ctx)
#override
_MyFABState createState() => _MyFABState();
}
class _MyFABState extends State<MyFAB> {
#override
Widget build(BuildContext context) {
return FloatingActionButton(
...
onPressed: () => show(),
);
}
void show() {
showModalBottomSheet(
...
context: context,
builder: (BuildContext context) {
return Wrap(
children: [
...
FlatButton(
onPressed: Provider.of<MyProvider>(widget.ctx, listen: false).doSomething(); //Can't do this
Navigator.pop(context);
)
],
);
}
);
}
}
In my opinion: showModalBottomSheet builds a bottom sheet with context which comes from Material App
1st image
so when we return any Widget to show in the Bottom sheet it uses that Material app context as we can see in the builder property in the:1st image.
2ng Image: your code
so in your code, when you are writing: Provider.of(context, listen: false).doSomething(); it is using context from the builder: (BuildContext context) which is the context of Material App. we have to change this context in order to use this Provider without having to uplift the position of our Provider above the Material App.
Now if we want to keep using that context to get the benefits of that overlay and automatic detection of suitable themes and still want to use the context of a widget that does have access to our provider:
we can pass the context of the Widget which does have Provider access to the FAB, but we will have to keep passing that context through widgets till we need to use that Provider in our FAB or till we go to a different route: in which case we can start from a new context and provider as Providers are scoped in mature.
so in your HomePage either you can wrap your scaffold inside a Builder or you can create a new widget like this:"
3rd image
so that it will have its own context which does have access to the provider we need inside our FAB as shown below in 4th image:
4th image
and then in the builder property of showModalBottomSheet change the name of the parameter in an anonymous function so that it won't be confused with the MAterial App context and context we will be passing in (Builder context or IdeaScreen context in my case image 4th)
5th image
I am creating a new widget but you do not have need to do so you can directly write your Fab code inside the anonymous function:
and can use context(not newContext which is related to Material App context) while calling the Provider as you are already doing.
But I will show in my case What I am doing in my AddTask Widget in case anyone's use case is similar to mine:
6th image
expect a context, which does have a provider access, I my case its context of IdeaScreen.
and then use it just like this:
7th image
I have a parent widget "BookmarkedShows" and child widget "ListOfShows". From child widget, when user taps on list item, it opens details page. When the user removes the show from bookmark from details page, on pressing back button, the show is not removed from the listing page. ie the parent is not refreshed. I'm using BlocBuilder.
There are some options mentioned in other question to add .then() to Navigator.push() method. However Navigator.push() happens in children component. How would I force refresh parent BlocBuilder during Navigation.pop()?
Parent "BookmarkedShows":
class BookmarkedShows extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => BookmarkShowsBloc()..add(LoadBookmarkedShows()),
child: BlocBuilder<BookmarkShowsBloc, BookmarkedShowsState>(
builder: (BuildContext context, BookmarkedShowsState state) {
return ShowList("Bookmarked shows", state.shows)
}),
);
}
}
Child "ListOfShows":
class ListOfShows extends StatelessWidget {
final String listName;
final List<Show> shows;
const ListOfShows(this.listName, this.shows);
#override
Widget build(BuildContext context) {
return Wrap(children: shows.map((show) => showItem(show, context)).toList());
}
InkWell showItem(Show show, BuildContext context) {
return InkWell(
onTap: () async {
await Navigator.of(context).push(MaterialPageRoute(
builder: (context) => showDetails(show)));
},
child: Container(
CachedNetworkImage(
imageUrl: show.portraitPoster
),
));
}
}
The question stated is a bit unclear, but I'm going to answer it the best I can.
If you want your widget to be able to update you need to make it Stateful.
Make your BookmarkedShows Widget Stateful:
class BookmarkedShows extends StatefulWidget {
BookmarkedShows ({Key key}) : super(key: key); //Can also work without this line
#override
StatefulBookmarkedShows createState() => StatefulBookmarkedShows();
}
class StatefulBookmarkedShows extends State<BookmarkedShows> {
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => BookmarkShowsBloc()..add(LoadBookmarkedShows()),
child: BlocBuilder<BookmarkShowsBloc, BookmarkedShowsState>(
builder: (BuildContext context, BookmarkedShowsState state) {
return ShowList("Bookmarked shows", state.shows)
}),
);
}
}
On returning back to parent you could implement something like in this Flutter docs example which might help to update the parent when navigating back. The async method awaits a response back from the child(Navigator).
When returning back to the Stateful parent you can call this like in the above mentioned async method:
LoadBookmarkedShows();
setState(() { });
I hope it works. Goodluck.
First of, I do know how BLoC suppose to work, the idea behind it and I know the difference between BlocProvider() and BlocProvider.value() constructors.
For simplicity, my application has 3 pages with a widget tree like this:
App() => LoginPage() => HomePage() => UserTokensPage()
I want my LoginPage() to have access to UserBloc because i need to log in user etc. To do that, I wrap LoginPage() builder at App() widget like this:
void main() => runApp(App());
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'My App',
home: BlocProvider<UserBloc>(
create: (context) => UserBloc(UserRepository()),
child: LoginPage(),
),
);
}
}
That obviously works just fine. Then, if User logs in successfully, he is navigated to HomePage. Now, I need to have access to two different blocs at my HomePage so I use MultiBlocProvider to pass existing UserBloc further and create a brand new one named DataBloc. I do it like this:
#override
Widget build(BuildContext context) {
return BlocListener<UserBloc, UserState>(
listener: (context, state) {
if (state is UserAuthenticated) {
Navigator.of(context).push(
MaterialPageRoute<HomePage>(
builder: (_) => MultiBlocProvider(
providers: [
BlocProvider.value(
value: BlocProvider.of<UserBloc>(context),
),
BlocProvider<DataBloc>(
create: (_) => DataBloc(DataRepository()),
),
],
child: HomePage(),
),
),
);
}
},
[...]
This also works. Problem happens when from HomePage user navigates to UserTokensPage. At UserTokensPage I need my already existing UserBloc that I want to pass with BlocProvider.value() constructor. I do it like this:
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: false,
title: Text('My App'),
actions: <Widget>[
CustomPopupButton(),
],
),
[...]
class CustomPopupButton extends StatelessWidget {
const CustomPopupButton({
Key key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return PopupMenuButton<String>(
icon: Icon(Icons.more_horiz),
onSelected: (String choice) {
switch (choice) {
case PopupState.myTokens:
{
Navigator.of(context).push(
MaterialPageRoute<UserTokensPage>(
builder: (_) => BlocProvider.value(
value: BlocProvider.of<UserBloc>(context),
child: UserTokensPage(),
),
),
);
}
break;
case PopupState.signOut:
{
BlocProvider.of<UserBloc>(context).add(SignOut());
Navigator.of(context).pop();
}
}
},
[...]
When I press button to navigate to MyTokensPage i get error with message:
════════ Exception caught by widgets library ═══════════════════════════════════════════════════════
The following assertion was thrown building Builder(dirty):
BlocProvider.of() called with a context that does not contain a Bloc of type UserBloc.
No ancestor could be found starting from the context that was passed to BlocProvider.of<UserBloc>().
This can happen if:
1. The context you used comes from a widget above the BlocProvider.
2. You used MultiBlocProvider and didn't explicity provide the BlocProvider types.
Good: BlocProvider<UserBloc>(create: (context) => UserBloc())
Bad: BlocProvider(create: (context) => UserBloc()).
The context used was: CustomPopupButton
What am I doing wrong? Is it because i have extracted PopupMenuButton widget that somehow loses blocs? I don't understand what I can be doing wrong.
You can just wrap the Blocs you need to access through out the app by wrapping it at the entry point of the app like this
runApp(
MultiBlocProvider(
providers: [
BlocProvider<UserBloc>(
create: (context) =>
UserBloc(UserRepository()),
),
],
child: App()
)
);
}
and you can access this bloc at anywhere of your app by
BlocProvider.of<UserBloc>(context).add(event of user bloc());
EDIT 10/03/2022
Since this thread became very popular I feel I need to add some comments.
This is valid solution if your goal is to use blocs that are not provided above your MaterialApp widget, but instead being declared somewhere down the widget tree by wrapping your widget (eg. some page) with BlocProvider making it possible for that widget to access the bloc.
It is easier to avoid problems by declaring all your blocs in MultiBlocProvider somewhere up the widget tree (like I said before), but this topic was not created with that in mind. Feel free to upvote and use this aproach described in Amesh Fernando response but do that knowing the difference.
I fixed it. Inside App widget i create LoginPage with
home: BlocProvider<UserBloc>(
create: (context) => UserBloc(UserRepository()),
child: LoginPage(),
At LoginPage I simply wrap BlocBuilders one into another
Widget build(BuildContext context) {
return BlocListener<UserBloc, UserState>(
listener: (context, state) {
if (state is UserAuthenticated) {
Navigator.of(context).push(
MaterialPageRoute<HomePage>(
builder: (_) => BlocProvider.value(
value: BlocProvider.of<UserBloc>(context),
child: BlocProvider<NewRelicBloc>(
create: (_) => NewRelicBloc(NewRelicRepository()),
child: HomePage(),
),
),
),
);
}
},
[...]
PopupMenuButton navigates User to TokenPage with
Navigator.of(context).push(
MaterialPageRoute<UserTokensPage>(
builder: (_) => BlocProvider.value(
value: BlocProvider.of<UserBloc>(context),
child: UserTokensPage(),
),
),
);
And that solved all my problems.
Solution
Method A: Access UserBloc provider instance directly without passing it
I prefer this solution since it requires less code.
A.1 Wrap CustomPopupButton instance with provider Consumer so it rebuilds itself whenever UserBloc notifies listeners of value changes.
Change this:
actions: <Widget>[
CustomPopupButton(),
],
To:
actions: <Widget>[
Consumer<UserBloc>(builder: (BuildContext context, UserBloc userBloc, Widget child) {
return CustomPopupButton(),
});
],
A.2 Change Provider instance invocation inside the stateless widget to disable listening to value changes -- "listening" and resulting "rebuilds" are already done by Consumer.
A.2.1 Change this:
value: BlocProvider.of<UserBloc>(context),
To:
value: BlocProvider.of<UserBloc>(context, listen: false),
A.2.2 And change this:
BlocProvider.of<UserBloc>(context).add(SignOut());
To:
BlocProvider.of<UserBloc>(context, listen: false).add(SignOut());
Method B: pass UserBloc provider instance
Same thing as Method A, but:
In A.1 you'd pass userBloc like this: return CustomPopupButton(userBloc: userBloc),.
You'd declare final UserBloc userBloc; member property inside CustomPopupButton.
In A.2 you'd do this: userBloc.add(SignOut()); instead of BlocProvider.of<UserBloc>(context, listen: false).add(SignOut());
Explanation
flutter_bloc is using Provider, to be aware what's going on it's better understand Provider. Please refer to my answer here to understand my answer to your question, and to understand Provider and listen flag better.
Change name of context in builder whether in bottomSheet or materialPageRoute.
So that bloc can access parent context through context
unless it's going to take context from builder (bottom sheet). This can lead
to an error which you can't reach the instance of bloc .
showModalBottomSheet(
context: context,
builder: (context2) { ===> change here to context2
BlocProvider.value(
value: BlocProvider.of<BlocA>(context),
child: widgetA(),
),
}
You need to either decompose your widget into two widgets (which I recommend for testability reasons) or use a Builder widget to get a child context.
class MyHomePage extends StatelessWidget { #override Widget build(BuildContext context) { return BlocProvider( create: (_) => TestCubit(), child: MyHomeView(), ); } } class MyHomeView extends StatelessWidget { #override Widget build(BuildContext context) { return Scaffold( body: Center( child: RaisedButton(onPressed: () => BlocProvider.of<TestCubit>(context)...) ), ); } }
source: solved by Felix Angelov, https://github.com/felangel/bloc/issues/2064
you don't have to use BlocProvider.value() to navigate to another screen, you can just wrap MaterialApp into BlocProvider as a child of it