07-21-2023, 09:55 PM
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:
```Dart
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:
```Dart
@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:
```Dart
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.
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:
```Dart
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:
```Dart
@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:
```Dart
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.