[英]BlocProvider.of() called with a context that does not contain a Bloc - even that it does
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.首先,我知道 BLoC 应该如何工作,它背后的想法,我知道
BlocProvider()
和BlocProvider.value()
构造函数之间的区别。
For simplicity, my application has 3 pages with a widget tree like this:为简单起见,我的应用程序有 3 个页面,其中包含一个像这样的小部件树:
App()
=> LoginPage()
=> HomePage()
=> UserTokensPage()
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:我希望我的
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(),
),
);
}
}
That obviously works just fine.这显然工作得很好。 Then, if User logs in successfully, he is navigated to
HomePage
.然后,如果用户成功登录,他将被导航到
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
.现在,我需要在我访问两个不同的集团
HomePage
,所以我用MultiBlocProvider
通过现有UserBloc
进一步,创造一个全新的命名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
.当从
HomePage
用户导航到UserTokensPage
时会发生问题。 At UserTokensPage
I need my already existing UserBloc
that I want to pass with BlocProvider.value()
constructor.在
UserTokensPage
我需要我已经存在的UserBloc
,我想用BlocProvider.value()
构造函数传递它。 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:当我按下按钮导航到
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
What am I doing wrong?我究竟做错了什么? Is it because i have extracted
PopupMenuButton
widget that somehow loses blocs?是不是因为我提取的
PopupMenuButton
小部件以某种方式丢失了块? I don't understand what I can be doing wrong.我不明白我做错了什么。
I fixed it.我修好了它。 Inside
App
widget i create LoginPage
with在
App
小部件中,我创建了LoginPage
home: BlocProvider<UserBloc>(
create: (context) => UserBloc(UserRepository()),
child: LoginPage(),
At LoginPage
I simply wrap BlocBuilders
one into another在
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
navigates User to TokenPage
with PopupMenuButton
用户导航到TokenPage
Navigator.of(context).push(
MaterialPageRoute<UserTokensPage>(
builder: (_) => BlocProvider.value(
value: BlocProvider.of<UserBloc>(context),
child: UserTokensPage(),
),
),
);
And that solved all my problems.这解决了我所有的问题。
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您可以像这样将其包装在应用程序的入口点,从而将您需要通过应用程序访问的 Blocs 包装起来
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());
UserBloc
provider instance directly without passing itUserBloc
提供者实例而不传递它I prefer this solution since it requires less code.我更喜欢这个解决方案,因为它需要更少的代码。
CustomPopupButton
instance with provider Consumer so it rebuilds itself whenever UserBloc notifies listeners of value changes. CustomPopupButton
实例,以便在 UserBloc 通知侦听器值更改时重建自身。 Change this:改变这个:
actions: <Widget>[
CustomPopupButton(),
],
To:到:
actions: <Widget>[
Consumer<UserBloc>(builder: (BuildContext context, UserBloc userBloc, Widget child) {
return CustomPopupButton(),
});
],
Consumer
. Consumer
完成。 A.2.1 Change this: A.2.1改变这一点:
value: BlocProvider.of<UserBloc>(context),
To:到:
value: BlocProvider.of<UserBloc>(context, listen: false),
A.2.2 And change this: A.2.2并更改此:
BlocProvider.of<UserBloc>(context).add(SignOut());
To:到:
BlocProvider.of<UserBloc>(context, listen: false).add(SignOut());
UserBloc
provider instanceUserBloc
提供程序实例Same thing as Method A, but:与方法 A 相同,但:
userBloc
like this: return CustomPopupButton(userBloc: userBloc),
.userBloc
像这样传递userBloc
: return CustomPopupButton(userBloc: userBloc),
。final UserBloc userBloc;
final UserBloc userBloc;
member property inside CustomPopupButton
. CustomPopupButton
内的成员属性。userBloc.add(SignOut());
userBloc.add(SignOut());
instead of BlocProvider.of<UserBloc>(context, listen: false).add(SignOut());
BlocProvider.of<UserBloc>(context, listen: false).add(SignOut());
flutter_bloc
is using Provider
, to be aware what's going on it's better understand Provider. flutter_bloc
正在使用Provider
,要知道发生了什么,最好了解 Provider 。 Please refer to my answer here to understand my answer to your question, and to understand Provider and listen
flag better.请参考我在此处的回答以了解我对您问题的回答,并更好地了解 Provider 和
listen
flag。
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.您需要将您的小部件分解为两个小部件(出于可测试性的原因,我建议这样做)或使用 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)...) ), ); } }
source: solved by Felix Angelov, https://github.com/felangel/bloc/issues/2064来源:由 Felix Angelov 解决, https://github.com/felangel/bloc/issues/2064
您不必使用 BlocProvider.value() 导航到另一个屏幕,您只需将 MaterialApp 包装到 BlocProvider 作为它的子项
Change name of context in builder whether in bottomSheet or materialPageRoute.更改构建器中上下文的名称,无论是在 bottomSheet 还是 materialPageRoute 中。
So that bloc can access parent context through context unless it's going to take context from builder (bottom sheet).这样 bloc 就可以通过上下文访问父上下文,除非它要从构建器(底部工作表)获取上下文。 This can lead to an error which you can't reach the instance of bloc .
这可能会导致您无法访问 bloc 实例的错误。
showModalBottomSheet(
context: context,
builder: (context2) { ===> change here to context2
BlocProvider.value(
value: BlocProvider.of<BlocA>(context),
child: widgetA(),
),
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.