在initState方法中获取Flutter的context。
在initState方法中获取Flutter的context。
我不确定initState
是否适合这个功能。\n我想要实现的是,在页面渲染时进行一些检查,并根据这些检查打开一个AlertDialog
进行一些设置。\n我有一个带有状态的页面。\n它的initState
函数如下所示:\n@override\nvoid initState() {\n super.initState();\n if (!_checkConfiguration()) {\n _showConfiguration(context);\n }\n}\n
\n_showConfiguration
函数如下所示:\nvoid _showConfiguration(BuildContext context) {\n AlertDialog dialog = new AlertDialog(\n content: new Column(\n children:
\n如果有更好的方法来进行这些检查,并在需要时调用模态框,请指导我正确的方向,我正在寻找一个onState
或onRender
函数,或者一个可以分配给build
函数的回调函数,在渲染时调用,但是我找不到一个。\n
\n编辑: 在这里他们有一个类似的问题: Flutter Redirect to a page on initState
Flutter中,当我们需要在initState()中获取上下文context以便与"Provider"一起使用时,应将参数listen设置为false。这是有道理的,因为我们不应该监听initState()阶段。因此,例如,应该是:
final settingData = Provider.of
大多数在这个线程中的initState()的示例可能适用于"UI"方面的事物,比如"Dialog",这是该线程的根问题。但不幸的是,当我尝试将其应用于为"Provider"获取上下文时,它对我不起作用。
因此,我选择了didChangeDependencies()方法。正如接受的答案中提到的,它有一个注意事项,即在小部件的生命周期中可能会被多次调用。然而,它很容易处理。只需使用一个bool类型的单个辅助变量来防止在didChangeDependencies()中的多次调用。这是使用_isInitialized作为主要"停止器"的_BookListState类的示例用法:
class _BookListState extends State
List
String _apiHost;
bool _isInitialized; //这是关键
bool _isFetching;
void didChangeDependencies() {
final settingData = Provider.of
this._apiHost = settingData.setting.apiHost;
final bookListData = Provider.of
this._bookList = bookListData.list;
this._isFetching = bookListData.isFetching;
if (this._isInitialized == null || !this._isInitialized) {// 仅执行一次
bookListData.fetchList(context);
this._isInitialized = true; // 将其设置为true以防止在此根块中使用"if()"的下一次执行
}
super.didChangeDependencies();
}
...
}
当我尝试使用initState()方法时,这是错误日志:
E/flutter ( 3556): [ERROR:flutter/lib/ui/ui_dart_state.cc(177)] Unhandled Exception: 'package:provider/src/provider.dart': Failed assertion: line 242 pos 7: 'context.owner.debugBuilding ||
E/flutter ( 3556): listen == false ||
E/flutter ( 3556): debugIsInInheritedProviderUpdate': Tried to listen to a value exposed with provider, from outside of the widget tree.
E/flutter ( 3556):
E/flutter ( 3556): This is likely caused by an event handler (like a button's onPressed) that called
E/flutter ( 3556): Provider.of without passing `listen: false`.
E/flutter ( 3556):
E/flutter ( 3556): To fix, write:
E/flutter ( 3556): Provider.of
E/flutter ( 3556):
E/flutter ( 3556): It is unsupported because may pointlessly rebuild the widget associated to the
E/flutter ( 3556): event handler, when the widget tree doesn't care about the value.
E/flutter ( 3556):
E/flutter ( 3556): The context used was: BookList(dependencies: [_InheritedProviderScope
E/flutter ( 3556):
E/flutter ( 3556): #0 _AssertionError._doThrowNew (dart:core-patch/errors_patch.dart:46:39)
E/flutter ( 3556): #1 _AssertionError._throwNew (dart:core-patch/errors_patch.dart:36:5)
E/flutter ( 3556): #2 Provider.of
package:provider/src/provider.dart:242
E/flutter ( 3556): #3 _BookListState.initState.
package:perpus/…/home/book-list.dart:24
E/flutter ( 3556): #4 new Future.delayed.
E/flutter ( 3556): #5 _rootRun (dart:async/zone.dart:1182:47)
E/flutter ( 3556): #6 _CustomZone.run (dart:async/zone.dart:1093:19)
E/flutter ( 3556): #7 _CustomZone.runGuarded (dart:async/zone.dart:997:7)
E/flutter ( 3556): #8 _CustomZone.bindCallbackGuarded.
E/flutter ( 3556): #9 _rootRun (dart:async/zone.dart:1190:13)
E/flutter ( 3556): #10 _CustomZone.run (dart:async/zone.dart:1093:19)
E/flutter ( 3556): #11 _CustomZone.bindCallback.
E/flutter ( 3556): #12 Timer._createTimer.
E/flutter ( 3556): #13 _Timer._runTimers (dart:isolate-patch/timer_impl.dart:397:19)
E/flutter ( 3556): #14 _Timer._handleMessage (dart:isolate-patch/timer_impl.dart:428:5)
E/flutter ( 3556): #15 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:168:12)
E/flutter ( 3556):
你之所以出现这个错误,是因为你没有使用"listen: false"参数。Provider检测到它不是从小部件树(在"build"方法内部)调用的。
感谢你指出这一点,看起来我确实使用了"listen: false"或context.read(),但是我却在VSCode上进行了"hot reload"而不是"restart"。在收到你的消息后,我尝试了在应用了"listen: false"后进行"restart"。我确认它确实是由"listen: true"或context.watch()引起的。我会尽快更新我的回答。
Flutter中,在initState方法中获取context会导致错误,因为在Widget生命周期的早期阶段,context还没有完全准备好。为了解决这个问题,可以使用Future.delayed方法来推迟获取context的操作,以确保在context准备好之后再执行相应的操作。
具体来说,可以在initState方法中使用Future.delayed方法来延迟执行_store.fetchContent(context)操作,如下所示:
void initState() {
super.initState();
_store = Store();
new Future.delayed(Duration.zero,() {
_store.fetchContent(context);
});
}
这样,_store.fetchContent(context)操作将会在context准备好之后执行,避免了在早期阶段获取不到context的问题。
需要注意的是,上述代码中的Store是一个自定义的状态类,用于处理数据的获取和管理。如果在代码中无法导入Store类,可能是因为相关的引入语句或文件路径不正确,需要进行修正。
通过使用Future.delayed方法来推迟在initState方法中获取context的操作,可以解决Flutter get context in initState method的问题。这种方法可以确保在context准备好之后再执行相应的操作,避免了在早期阶段获取不到context的错误。
在Flutter中,我们经常会遇到在`initState`方法中获取`context`的问题。根据Flutter的文档,我们不能在`initState`方法中使用`BuildContext.inheritFromWidgetOfExactType`,但是可以在`didChangeDependencies`方法中使用。
为了解决这个问题,我们可以将初始化逻辑移到`didChangeDependencies`方法中,但是这可能不完全符合我们的需求,因为`didChangeDependencies`方法在widget的生命周期中可能会被多次调用。
如果我们使用异步调用来延迟操作,直到widget被初始化后再执行,就可以在`initState`方法中使用`context`了。一个简单的方法是使用`Future`,具体实现如下:
Future.delayed(Duration.zero,() {
... showDialog(context, ....)
});
另一种更加"正确"的方法是使用Flutter的scheduler添加一个post-frame回调,具体实现如下:
SchedulerBinding.instance.addPostFrameCallback((_) {
... showDialog(context, ....)
});
最后,这里有一个我喜欢在`initState`函数中使用异步调用的小技巧:
() async {
await Future.delayed(Duration.zero);
... showDialog(context, ...)
}();
下面是一个完整的示例,使用了简单的`Future.delayed`:
import 'dart:async';
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State
int _counter = 0;
bool _checkConfiguration() => true;
void initState() {
super.initState();
if (_checkConfiguration()) {
Future.delayed(Duration.zero,() {
showDialog(
context: context,
builder: (context) => AlertDialog(
content: Column(
children:
Text('')
],
),
actions:
FlatButton(
onPressed: (){
Navigator.pop(context);
},
child: Text('OK')),
],
)
);
});
}
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children:
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),
);
}
}
根据问题的补充信息,我可以给出一个稍微更好的解决方案。根据应用程序的情况,您可能希望根据应用程序第一次打开的情况来决定显示哪个页面,即将`home`设置为不同的值。而对话框在移动设备上不一定是最好的UI元素;最好的做法可能是显示一个包含所需设置和下一步按钮的完整页面。
由于问题中的页面是通过`MaterialApp`的`home`属性调用的第一个页面,所以实际上我在那里并没有进行推送。您可以在`build`函数中给我一个示例吗?目前它只返回一个新的带有`appBar`、`drawer`、`body`和`floatingActionButton`的`Scaffold`。
我能想到的一个方法是将模态框中使用的所有小部件外包到一个函数中,在主页的`Scaffold`中显示它们(作为非模态),如果检查失败,则只显示主页。但是这对我来说感觉有点hacky。
这很不好。您可以在`didChangeDependencies`方法中访问`context`,这是您可以访问`context`的第一个地方。
我稍微修改了示例。我实际上忘记了`context`实际上是状态的成员变量。所以您不需要布尔值,您可以直接在`initState`中使用`Future.delayed`。不过,这仍然是必需的 - 如果没有它,您将在尝试推送时得到断言错误。
我还在最后添加了另一个建议,我认为这将是一个更好的解决方案。我尽量尽量少使用弹出对话框 - 在移动设备上,显示一个完整页面通常更好。
在我的情况下,在`initState`中显示"Undefined name context"。
`Future.delayed(Duration.zero,() {})`函数是否总是在`build`方法之后运行?如果在`initState`中添加非`Future`或其他`Future`方法会怎样?您是否知道任何可能出现的问题?我实现了您的示例,到目前为止一切都正常。
`SchedulerBinding.instance.addPostFrameCallback((_) { ... showDialog(context, ....) });`方法工作得很完美。我从未知道在构建之后可以使用`context`。太棒了!