我在使用Flutter Provider时遇到问题... 我的流程是这样的:将登录用户ID传递到新的小部件后,->从那里将其预先保存到db,然后将其重定向到新的小部件(仪表板)。
这是登录后的小部件代码:
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));
}
我知道这个主题已经解决了问题,但是找不到适合我问题的东西。
答案 0 :(得分:32)
MaterialApp
> provider(Screen A)
> Screen B
如果 Provider
在屏幕 A 中实例化,则在从 A → B 执行 Navigator.push
后,它将无法在屏幕 B 中访问。
因为 Provider
是 InheritedWidget
并且 Navigator
在其 Screen A MaterialApp context
范围之外使用 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
需要成为任何想要访问“提供的”状态对象的小部件树的根小部件。Navigator.push(context)
不使用屏幕 A 上的 context
。
context
的 MaterialApp
。Navigator.push(context)
实际上是 Navigator.of(context).push
Navigator.of(context)
表示:向上搜索这个上下文层次结构,直到找到一个实例化导航器的上下文
Navigator
在 MaterialApp
中实例化。Navigator
,否则您使用的是默认值。Navigator
的 context
是 MaterialApp
的。MaterialApp
),不是屏幕 A 的上下文。
context
A 中看起来是“实例化的”。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。
答案 1 :(得分:1)
尝试将位于脚手架下方的小部件树的一部分提取到单独的小部件。 您现在使用的上下文用于构建您的顶级窗口小部件,而该窗口小部件还没有Navigator。
生成的代码应如下所示:
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);
},
...
}
}
答案 2 :(得分:1)
参数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 :(得分:0)
也许在DetailScreen
回答。
我不知道你是否解决了这个问题。但我只是使用参数解决了。
首先我的问题是,我有一个 list screen
、一个 detail screen
和一个 listBloc
。我在 listBloc
中提供了 list screen
。像这样:
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(),
);
}
}
而且,我需要在 listBloc
的 detail screen
中使用 BlocBuilder
。
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) {}
);
}
}