BlocProvider.of() 使用不包含 Bloc 的上下文调用 - 即使它包含



首先,我知道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(),
),
);
}
}

这显然很好用。然后,如果用户成功登录,他将导航到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我需要我想通过构造函数传递的现有UserBlocBlocProvider.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以某种方式丢失集团的小部件吗?我不明白我可能做错了什么。

您可以通过将其包装在应用程序的入口点来包装您需要访问的 Bloc

,如下所示
runApp(
MultiBlocProvider(
providers: [
BlocProvider<UserBloc>(
create: (context) =>
UserBloc(UserRepository()),
),
],
child: App()
)
);
}

您可以在应用程序的任何位置通过以下方式访问此集团

BlocProvider.of<UserBloc>(context).add(event of user bloc());

编辑 10/03/2022

由于这个线程变得非常流行,我觉得我需要添加一些评论。

如果您的目标是使用未在MaterialApp小部件上方提供的块,而是通过包装您的小部件(例如某个页面BlocProvider(来声明小部件树下方的某个位置,从而使该小部件可以访问该集团,那么这是有效的解决方案。

通过在小部件树的某个地方MultiBlocProvider声明所有 loc 来更容易避免问题(就像我之前说的(,但创建本主题时并没有考虑到这一点。随意投票并使用Amesh Fernando回应中描述的这个aproach,但要知道区别。

<小时 />

我修好了。在我创建LoginPageApp小部件中

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(),
),
),
);

这解决了我所有的问题。

在构建器中更改上下文的名称,无论是在 bottomSheet 还是 materialPageRoute 中。

以便 bloc 可以通过上下文访问父上下文 除非它要从构建器(底表(获取上下文。这可能导致 到无法访问 bloc 实例的错误。

showModalBottomSheet(
context: context,
builder: (context2) {  ===> change here to context2
BlocProvider.value(
value: BlocProvider.of<BlocA>(context),
child: widgetA(),
),
}

解决方案

方法 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 在无状态小部件中调用 Change Provider 实例以禁用对值更改的侦听 -- "侦听"和生成的"重建"已经由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 中,您将像这样传递userBlocreturn CustomPopupButton(userBloc: userBloc),.
  • 您将在CustomPopupButton中声明final UserBloc userBloc;成员属性。
  • 在 A.2 中,您将执行以下操作:userBloc.add(SignOut());而不是BlocProvider.of<UserBloc>(context, listen: false).add(SignOut());

解释

flutter_bloc正在使用Provider,以了解正在发生的事情,最好了解提供者。请参阅此处的回答,以了解我对您问题的回答,并更好地了解提供者和listen标志。

您需要将小部件分解为两个小部件(出于可测试性的原因,我建议这样做(或使用 Builder 小部件来获取子上下文。

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)...) ), ); 
} 
}

来源:由费利克斯·安杰洛夫解决,https://github.com/felangel/bloc/issues/2064

您不必使用BlocProvider.value((导航到另一个屏幕,您只需将MaterialApp包装到BlocProvider中

即可。

相关内容

  • 没有找到相关文章

最新更新