BlocProvider.of() 在不包含 Bloc 的上下文中调用 - 即使它包含
BlocProvider.of() called with a context that does not contain a Bloc - even that it does
首先,我确实知道 BLoC 应该如何工作,它背后的想法,我知道 BlocProvider()
和 BlocProvider.value()
构造函数之间的区别。
为简单起见,我的应用程序有 3 个页面,其中的小部件树如下:
App()
=> LoginPage()
=> HomePage()
=> UserTokensPage()
我希望我的 LoginPage()
能够访问 UserBloc
,因为我需要登录用户等。为此,我将 LoginPage()
构建器包装在 App()
小部件中像这样:
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(),
),
);
}
}
这显然工作得很好。然后,如果 User 成功登录,他将被导航到 HomePage
。现在,我需要在 HomePage
访问两个不同的区块,所以我使用 MultiBlocProvider
进一步传递现有的 UserBloc
并创建一个名为 DataBloc
的全新区块。我是这样做的:
@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(),
),
),
);
}
},
[...]
这也行。从 HomePage
用户 导航到 UserTokensPage
时会出现问题。在 UserTokensPage
我需要我已经存在的 UserBloc
我想用 BlocProvider.value()
构造函数传递。我是这样做的:
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();
}
}
},
[...]
当我按下按钮导航到 MyTokensPage
时,我收到错误消息:
════════ 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
我做错了什么?是因为我提取了 PopupMenuButton
小部件以某种方式丢失了 blocs 吗?我不明白我做错了什么。
解决方案
方法 A:直接访问 UserBloc
提供者实例而不传递它
我更喜欢这个解决方案,因为它需要的代码更少。
A.1 使用提供程序 Consumer 包装 CustomPopupButton
实例,因此只要 UserBloc 通知侦听器值更改,它就会自行重建。
改变这个:
actions: <Widget>[
CustomPopupButton(),
],
收件人:
actions: <Widget>[
Consumer<UserBloc>(builder: (BuildContext context, UserBloc userBloc, Widget child) {
return CustomPopupButton(),
});
],
A.2 更改无状态小部件内的 Provider 实例调用以禁用侦听值更改 -- "listening" 和结果 "rebuilds" 已由 Consumer
完成。
A.2.1 更改:
value: BlocProvider.of<UserBloc>(context),
收件人:
value: BlocProvider.of<UserBloc>(context, listen: false),
A.2.2 并更改为:
BlocProvider.of<UserBloc>(context).add(SignOut());
收件人:
BlocProvider.of<UserBloc>(context, listen: false).add(SignOut());
方法 B:传递 UserBloc
提供商实例
与方法 A 相同,但是:
- 在 A.1 中,您将像这样传递
userBloc
:return CustomPopupButton(userBloc: userBloc),
.
- 您将在
CustomPopupButton
. 中声明 final UserBloc userBloc;
成员 属性
- 在 A.2 中你会这样做:
userBloc.add(SignOut());
而不是 BlocProvider.of<UserBloc>(context, listen: false).add(SignOut());
说明
flutter_bloc
正在使用 Provider
, to be aware what's going on it's better understand Provider. Please refer to my answer 来理解我对你问题的回答,并更好地理解 Provider 和 listen
标志。
编辑 2022 年 3 月 10 日
既然这个帖子很受欢迎,我觉得我需要添加一些评论。
如果您的目标是使用 不是 在 MaterialApp
小部件上方提供的块,而是通过包装在小部件树下的某处声明,则这是有效的解决方案您的小部件(例如某些页面)BlocProvider
使该小部件可以访问该集团。
通过在小部件树上方的 MultiBlocProvider
某处声明所有 bloc 可以更容易地避免问题(就像我之前说的那样),但是创建本主题时并没有考虑到这一点。随意投票并使用 Amesh Fernando 回复中描述的这种方法,但要知道其中的区别。
我修好了。在 App
小部件中,我用
创建了 LoginPage
home: BlocProvider<UserBloc>(
create: (context) => UserBloc(UserRepository()),
child: LoginPage(),
在 LoginPage
我只是将 BlocBuilders
一个包裹到另一个
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
使用
将 User 导航到 TokenPage
Navigator.of(context).push(
MaterialPageRoute<UserTokensPage>(
builder: (_) => BlocProvider.value(
value: BlocProvider.of<UserBloc>(context),
child: UserTokensPage(),
),
),
);
这解决了我所有的问题。
您不必使用 BlocProvider.value() 导航到另一个屏幕,您可以将 MaterialApp 作为它的子项包装到 BlocProvider 中
您可以像这样在应用程序的入口点包装您需要通过应用程序访问的 Blocs
runApp(
MultiBlocProvider(
providers: [
BlocProvider<UserBloc>(
create: (context) =>
UserBloc(UserRepository()),
),
],
child: App()
)
);
}
并且您可以通过
在您的应用程序的任何位置访问此集团
BlocProvider.of<UserBloc>(context).add(event of user bloc());
在 bottomSheet 或 materialPageRoute 中更改构建器中上下文的名称。
这样bloc就可以通过上下文访问父上下文
除非它要从构建器中获取上下文(底部 sheet)。这可以导致
一个你无法到达 bloc 实例的错误。
showModalBottomSheet(
context: context,
builder: (context2) { ===> change here to context2
BlocProvider.value(
value: BlocProvider.of<BlocA>(context),
child: widgetA(),
),
}
您需要将小部件分解为两个小部件(出于可测试性原因我建议这样做)或使用构建器小部件来获取子上下文。
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)...) ), ); } }
来源:由 Felix Angelov 解决,https://github.com/felangel/bloc/issues/2064
首先,我确实知道 BLoC 应该如何工作,它背后的想法,我知道 BlocProvider()
和 BlocProvider.value()
构造函数之间的区别。
为简单起见,我的应用程序有 3 个页面,其中的小部件树如下:
App()
=> LoginPage()
=> HomePage()
=> UserTokensPage()
我希望我的 LoginPage()
能够访问 UserBloc
,因为我需要登录用户等。为此,我将 LoginPage()
构建器包装在 App()
小部件中像这样:
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(),
),
);
}
}
这显然工作得很好。然后,如果 User 成功登录,他将被导航到 HomePage
。现在,我需要在 HomePage
访问两个不同的区块,所以我使用 MultiBlocProvider
进一步传递现有的 UserBloc
并创建一个名为 DataBloc
的全新区块。我是这样做的:
@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(),
),
),
);
}
},
[...]
这也行。从 HomePage
用户 导航到 UserTokensPage
时会出现问题。在 UserTokensPage
我需要我已经存在的 UserBloc
我想用 BlocProvider.value()
构造函数传递。我是这样做的:
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();
}
}
},
[...]
当我按下按钮导航到 MyTokensPage
时,我收到错误消息:
════════ 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
我做错了什么?是因为我提取了 PopupMenuButton
小部件以某种方式丢失了 blocs 吗?我不明白我做错了什么。
解决方案
方法 A:直接访问 UserBloc
提供者实例而不传递它
我更喜欢这个解决方案,因为它需要的代码更少。
A.1 使用提供程序 Consumer 包装 CustomPopupButton
实例,因此只要 UserBloc 通知侦听器值更改,它就会自行重建。
改变这个:
actions: <Widget>[
CustomPopupButton(),
],
收件人:
actions: <Widget>[
Consumer<UserBloc>(builder: (BuildContext context, UserBloc userBloc, Widget child) {
return CustomPopupButton(),
});
],
A.2 更改无状态小部件内的 Provider 实例调用以禁用侦听值更改 -- "listening" 和结果 "rebuilds" 已由 Consumer
完成。
A.2.1 更改:
value: BlocProvider.of<UserBloc>(context),
收件人:
value: BlocProvider.of<UserBloc>(context, listen: false),
A.2.2 并更改为:
BlocProvider.of<UserBloc>(context).add(SignOut());
收件人:
BlocProvider.of<UserBloc>(context, listen: false).add(SignOut());
方法 B:传递 UserBloc
提供商实例
与方法 A 相同,但是:
- 在 A.1 中,您将像这样传递
userBloc
:return CustomPopupButton(userBloc: userBloc),
. - 您将在
CustomPopupButton
. 中声明 - 在 A.2 中你会这样做:
userBloc.add(SignOut());
而不是BlocProvider.of<UserBloc>(context, listen: false).add(SignOut());
final UserBloc userBloc;
成员 属性
说明
flutter_bloc
正在使用 Provider
, to be aware what's going on it's better understand Provider. Please refer to my answer listen
标志。
编辑 2022 年 3 月 10 日
既然这个帖子很受欢迎,我觉得我需要添加一些评论。
如果您的目标是使用 不是 在 MaterialApp
小部件上方提供的块,而是通过包装在小部件树下的某处声明,则这是有效的解决方案您的小部件(例如某些页面)BlocProvider
使该小部件可以访问该集团。
通过在小部件树上方的 MultiBlocProvider
某处声明所有 bloc 可以更容易地避免问题(就像我之前说的那样),但是创建本主题时并没有考虑到这一点。随意投票并使用 Amesh Fernando 回复中描述的这种方法,但要知道其中的区别。
我修好了。在 App
小部件中,我用
LoginPage
home: BlocProvider<UserBloc>(
create: (context) => UserBloc(UserRepository()),
child: LoginPage(),
在 LoginPage
我只是将 BlocBuilders
一个包裹到另一个
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
使用
TokenPage
Navigator.of(context).push(
MaterialPageRoute<UserTokensPage>(
builder: (_) => BlocProvider.value(
value: BlocProvider.of<UserBloc>(context),
child: UserTokensPage(),
),
),
);
这解决了我所有的问题。
您不必使用 BlocProvider.value() 导航到另一个屏幕,您可以将 MaterialApp 作为它的子项包装到 BlocProvider 中
您可以像这样在应用程序的入口点包装您需要通过应用程序访问的 Blocs
runApp(
MultiBlocProvider(
providers: [
BlocProvider<UserBloc>(
create: (context) =>
UserBloc(UserRepository()),
),
],
child: App()
)
);
}
并且您可以通过
在您的应用程序的任何位置访问此集团BlocProvider.of<UserBloc>(context).add(event of user bloc());
在 bottomSheet 或 materialPageRoute 中更改构建器中上下文的名称。
这样bloc就可以通过上下文访问父上下文 除非它要从构建器中获取上下文(底部 sheet)。这可以导致 一个你无法到达 bloc 实例的错误。
showModalBottomSheet(
context: context,
builder: (context2) { ===> change here to context2
BlocProvider.value(
value: BlocProvider.of<BlocA>(context),
child: widgetA(),
),
}
您需要将小部件分解为两个小部件(出于可测试性原因我建议这样做)或使用构建器小部件来获取子上下文。
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)...) ), ); } }
来源:由 Felix Angelov 解决,https://github.com/felangel/bloc/issues/2064