在此小部件上方找不到正确的提供程序
Posted
技术标签:
【中文标题】在此小部件上方找不到正确的提供程序【英文标题】:Could not find the correct provider above this widget 【发布时间】:2019-11-29 03:28:44 【问题描述】:我在使用 Flutter Provider 时遇到问题... 我的流程是这样的:登录用户 ID 传递给新的小部件后 -> 从那里它预先保存到数据库,然后重定向到新的小部件(仪表板)。
这是登录后小部件的代码:
return MaterialApp(
title: title,
home: Scaffold(
appBar: AppBar(
title: Text(title),
),
body: ListView(
children: <Widget>[
Container(
margin: EdgeInsets.all(8.0),
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(8.0))),
child: InkWell(
onTap: ()
var user = Provider.of<UserRepository>(context);
user.savePreference(user.user.id, "Something");
user.navigateToNewPage(Dashboard(), context);
print(user.user.id);
,
这行得通:
user.savePreference(user.user.id, "Something");
但这导致了一个问题:
user.navigateToNewPage(Dashboard(), context);
在仪表板小部件中,我正在创建这个:
Widget build(BuildContext context)
var user = Provider.of<UserRepository>(context);
在 UserRepository 我有这个:
class UserRepository with ChangeNotifier
User user;
Status _status = Status.Uninitialized;
Status get status => _status;
User get getUser => user;
UserRepository.instance();
Future<void> navigateToNewPage(Widget page, BuildContext context)
Navigator.push(context, MaterialPageRoute(builder: (context) => page));
我知道这个话题已经解决了问题,但是找不到适合我的问题的东西。
【问题讨论】:
【参考方案1】:提供者范围
MaterialApp
> provider(Screen A)
> Screen B
如果 Provider
在屏幕 A 中被实例化,则在屏幕 B 中从 A → B 的 Navigator.push
之后将无法访问它。
为什么?
因为Provider
是InheritedWidget
,而Navigator
在其Screen A context
范围之外使用MaterialApp context
。 (请参阅下面的详细信息。)
修复
将Provider
移动到共同父级MaterialApp context
,允许屏幕A 和B 都继承其状态/上下文。
provider(MaterialApp)
> Screen A
> Screen B
示例
void main()
runApp(MyApp());
class MyApp extends StatelessWidget
@override
Widget build(BuildContext context)
/// wrap MaterialApp in Provider widget
return ChangeNotifierProvider(
create: (context) => ColorModel(), // ← create/init your state model
child: MaterialApp(
home: ScreenA()
),
);
详情
提供者
Provider
基于InheritedWidget
。只有子小部件可以继承父小部件的状态。
Provider
必须是任何想要访问“提供”状态对象的小部件树的根小部件。
导航器
屏幕 A 上的Navigator.push(context)
不使用屏幕 A 中的 context
。
它使用来自MaterialApp
的context
。
Navigator.push(context)
实际上是Navigator.of(context).push
Navigator.of(context)
表示:向上搜索此上下文层次结构,直到找到实例化 Navigator 的上下文
默认Navigator
在MaterialApp
中实例化。
除非您明确创建/使用不同的Navigator
,否则您将使用默认值。
Navigator
的 context
是 MaterialApp
的。
屏幕 B 将获得 那个 上下文(MaterialApp
),不是屏幕 A 的上下文。
B 是 A 的兄弟姐妹,而不是其孩子。
B 不会从 A 继承,即使它在 context
A 中显示为“实例化”。
屏幕 B 是MaterialApp
context
的子,而不是屏幕 A context
的子。
Provider
context
范围,如果在屏幕 A 中定义,则不涵盖屏幕 B
画面 A → B
Navigator.push(context, MaterialPageRoute(builder: (context) => ScreenB()))
其实是:
Navigator.of(context).push(MaterialPageRoute(builder: (context) => ScreenB()))
就像:
Navigator.of(MaterialApp).push(
MaterialPageRoute(builder: (MaterialAppContext) => ScreenB())
)
所以屏幕 B 在 MaterialApp context
下,不是 在屏幕 A context
下,因此无法访问屏幕 A Provider
及其 context
。
关于Provider
的类似问题请参见this answer for a code sample。
【讨论】:
如何添加多提供者? /// 在 Provider 小部件中包装 MaterialApp 对我有用 @AliYarKhan 我在下面评论了 multiprovider。 在那种情况下,所有提供者都需要放入根小部件吗? 我找不到一个有效的公共父亲小部件除了根小部件,这很奇怪【参考方案2】:Provider.of(context) 的参数context
必须是您定义的提供者的子级。
@override
Widget build(BuildContext context)
// !important here, Scaffold.of(context) returns null
return Scaffold(
appBar: AppBar(title: Text('Demo')),
body: Builder(
builder: (BuildContext context)
return FlatButton(
child: Text('BUTTON'),
onPressed: ()
// here, Scaffold.of(context) returns the locally created Scaffold
Scaffold.of(context).showSnackBar(SnackBar(
content: Text('Hello.')
));
);
)
);
官方样本:https://api.flutter.dev/flutter/widgets/BuildContext-class.html
这是我的代码:
@override
Widget build(BuildContext context)
return MultiProvider(
providers: [ChangeNotifierProvider<SomeModel>(
create: (context)
return SomeModel();
,
),],
child: Builder(builder: (BuildContext context)
BuildContext rootContext = context;
return Container(
//Here to use rootContext is safe
//Provider.of<SomeModel>(rootContext, listen: false);
);
),
);
【讨论】:
【参考方案3】:尝试将位于 Scaffold 下方的小部件树的一部分提取到单独的小部件中。 您现在使用的上下文用于构建尚未导航器的***小部件。
生成的代码应如下所示:
return MaterialApp(
title: title,
home: Scaffold(
appBar: AppBar(
title: Text(title),
),
body: LoginWidget()
class LoginWidget extends StatelessWidget
@override
Widget build(BuildContext context)
return ListView(
children: <Widget>[
Container(
margin: EdgeInsets.all(8.0),
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(8.0))),
child: InkWell(
onTap: ()
var user = Provider.of<UserRepository>(context);
user.savePreference(user.user.id, "Something");
user.navigateToNewPage(Dashboard(), context);
print(user.user.id);
,
...
【讨论】:
您能说得更具体些吗?【参考方案4】:这就是我们与多提供商一起使用的方式
class MyApp extends StatelessWidget
@override
Widget build(BuildContext context)
return MultiProvider(
providers: [
ChangeNotifierProvider(
create: (context) => GeneralProvider(),
),
],
child: MaterialApp(
home: InitPage(),
onGenerateRoute: MyRouter.generateRoute,
initialRoute: '/InitPage',
debugShowCheckedModeBanner: false,
title: 'Eray',
),
);
【讨论】:
【参考方案5】:可能会在DetailScreen
中回答。
我不知道你是否解决了这个问题。但我只是使用一个参数解决了。
首先我的问题是,我有一个list screen
、一个detail screen
和一个listBloc
。我在list screen
中提供了listBloc
。像这样:
class ListScreen extends StatelessWidget
static const String routeName = '/list_screen';
ListScreen(Key key) : super(key: key);
static Route route(Map<String, dynamic> args)
return MaterialPageRoute(
builder: (_) => ListScreen(),
settings: const RouteSettings(name: routeName),
);
@override
Widget build(BuildContext context)
return MultiBlocProvider(
providers: [
BlocProvider(
create: (context) =>ListBloc(),
)
]
child: DetailScreen(),
);
而且,我需要在detail screen
的BlocBuilder
中使用listBloc
。
class DetailScreen extends StatelessWidget
// you need a context
final BuildContext context;
// get context
const DetailScreen(
Key key,
@required this.context,
) : super(key: key);
@override
Widget build(BuildContext context)
// get bloc using your context
final bloc = this.context.read<ListBloc>();
return BlocBuilder<ListBloc, ListState>(
// provide your bloc
bloc: bloc,
builder: (context, state)
);
【讨论】:
【参考方案6】:我从错误的路径导入了我的 Provider:
import 'file:///D:/Projects/Flutter/library_app/lib/MyStateManagement/local/temp_management.dart';
当我更改路径的导入时,它对我的工作:
import 'package:library_app/MyStateManagement/local/temp_management.dart';
当我更改提供程序文件的文件夹时,这个问题发生在我身上。
【讨论】:
以上是关于在此小部件上方找不到正确的提供程序的主要内容,如果未能解决你的问题,请参考以下文章
错误:在此 UpdateSupervisor 小部件上方找不到正确的 Provider<NewUser>
ProviderNotFoundException(错误:在此主页小部件上方找不到正确的 Provider<EntryProvider>
错误:在此 Directioner 小部件上方找不到正确的 Provider<User>