有没有办法在 InitState 方法上加载异步数据?
Posted
技术标签:
【中文标题】有没有办法在 InitState 方法上加载异步数据?【英文标题】:Is there a way to load async data on InitState method? 【发布时间】:2019-01-24 19:51:58 【问题描述】:我正在寻找一种在 InitState 方法上加载异步数据的方法,在构建方法运行之前我需要一些数据。我正在使用 GoogleAuth 代码,我需要执行构建方法直到 Stream 运行。
我的initState方法是:
@override
void initState ()
super.initState();
_googleSignIn.onCurrentUserChanged.listen((GoogleSignInAccount account)
setState(()
_currentUser = account;
);
);
_googleSignIn.signInSilently();
如有任何反馈,我将不胜感激。
【问题讨论】:
StreamBuilder
是正确的解决方案
这段代码完全没问题?
既然initState()
只被调用一次,那么在里面使用setState()
有什么意义吗?
还可以考虑 FutyureBuilder,因为它在等待异步方法时会阻塞 once;其中流构建器模式是一系列未经请求的事件。
【参考方案1】:
您可以创建一个async
方法并在您的initState
中调用它
@override
void initState ()
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_)
_asyncMethod();
);
_asyncMethod() async
_googleSignIn.onCurrentUserChanged.listen((GoogleSignInAccount account)
setState(()
_currentUser = account;
);
);
_googleSignIn.signInSilently();
【讨论】:
不应该改变任何东西,除非他的电话是同步和即时的。但是没有异步问题 Ini 这个案例build
在_asyncMethod
完成之前执行!
@diegoveloper 使用WidgetsBinding.instance.addPostFrameCallback
而不是@Nae 答案的目的是什么?
在初始化状态时调用 setState() 是错误的,这会导致页面加载后刷新页面,因为您在 initState 中调用了异步方法。解决方案只有 FutureBuilder()。
不,没关系,因为我正在使用 WidgetsBinding.instance.addPostFrameCallback【参考方案2】:
截至目前,使用 .then
表示法似乎有效:
// ...
@override
initState()
super.initState();
myAsyncFunction
// as suggested in the comment
// .whenComplete()
// or
.then((result)
print("result: $result");
setState(() );
);
//...
【讨论】:
如果您的异步函数没有返回任何内容,请改用whenComplete
。
@Aravin 我个人更喜欢流。但这对于演示目的“有效”。我非常怀疑这是否是正确的方式。
我得到“未处理的异常:在 dispose() 之后调用 setState()”。
@Eradicatore 听起来小部件在你的异步函数完成之前就被处理掉了。
@ShouryaShikhar 流是构建小部件的事实上的方式,上面的答案只是异步加载的一种黑客方式,但管理小部件的生命周期等要困难得多。跨度>
【参考方案3】:
方法一:您可以使用StreamBuilder来做到这一点。这将在 stream 中的数据发生变化时运行 builder 方法。
下面是我的一个示例项目的代码 sn-p:
StreamBuilder<List<Content>> _getContentsList(BuildContext context)
final BlocProvider blocProvider = BlocProvider.of(context);
int page = 1;
return StreamBuilder<List<Content>>(
stream: blocProvider.contentBloc.contents,
initialData: [],
builder: (context, snapshot)
if (snapshot.data.isNotEmpty)
return ListView.builder(itemBuilder: (context, index)
if (index < snapshot.data.length)
return ContentBox(content: snapshot.data.elementAt(index));
else if (index / 5 == page)
page++;
blocProvider.contentBloc.index.add(index);
);
else
return Center(
child: CircularProgressIndicator(),
);
);
在上面的代码中,StreamBuilder 监听内容的任何变化,最初它是一个空数组并显示 CircularProgressIndicator。一旦我进行 API 调用,获取的数据就会添加到内容数组中,该数组将运行 builder 方法。
当用户向下滚动时,会获取更多内容并将其添加到内容数组中,该数组将再次运行 builder 方法。
在您的情况下,只需要初始加载。但这为您提供了在获取数据之前在屏幕上显示其他内容的选项。
希望这有帮助。
编辑:
在你的情况下,我猜它看起来如下所示:
StreamBuilder<List<Content>>(
stream: account, // stream data to listen for change
builder: (context, snapshot)
if(account != null)
return _googleSignIn.signInSilently();
else
// show loader or animation
);
方法 2: 另一种方法是创建一个 async
方法并从您那里调用它的 initState()
方法,如下所示:
@override
void initState()
super.initState();
asyncMethod();
void asyncMethod() async
await asyncCall1();
await asyncCall2();
// ....
【讨论】:
也许让你的 sn-p 适应他的流 @RémiRousselet 编辑了答案以添加您所要求的信息。请检查是否正确。 方法 2 简单易行,对我有用。谢谢 对于方法 2,有没有办法在等待调用结束时触发 setState()?我试试这个,我看到“mounted”现在是假的,所以 setState() 会产生一个错误。 @RémiRousselet【参考方案4】:在initState
中创建匿名函数,如下所示:
@override
void initState()
super.initState();
// Create anonymous function:
() async
await _performYourTask();
setState(()
// Update your UI with the desired changes.
);
();
【讨论】:
请鼓起勇气说出投票失败的原因。 这与直接调用_performYourTask();
有何不同?如何用另一个没有等待帮助的异步函数包装异步函数?
在上面的匿名函数中,你可以在_performYourTask
完成后调用setState
(你肯定不能直接在initState
中这样做)
因此,在所需的异步方法完成之后,额外的包装保留用于排队setState
调用。然后我建议在await
之后添加该行。因为现在它似乎没有做任何不同的事情。【参考方案5】:
上一个答案!!
您可以设置一个布尔值,如加载,并在您的监听函数中将其设置为 true,并使您的构建函数在加载设置为 true 时返回您的数据,否则只需抛出一个 CircularProgressIndicator
已编辑—— 我不建议在您在 initState 中调用的方法中调用 setState。如果在调用 setState 时(异步操作完成)未安装小部件,则会报告错误。我建议你使用一个包after_layout
查看此答案以更好地理解 initState 中的 setState:https://***.com/a/53373017/9206337
这篇文章将让您了解应用何时完成构建方法。这样您就可以在挂载小部件后等待您的异步方法设置状态:https://***.com/a/51273797/9206337
【讨论】:
【参考方案6】: @override
void initState()
super.initState();
asyncInitState(); // async is not allowed on initState() directly
void asyncInitState() async
await yourAsyncCalls();
【讨论】:
虽然此代码可能会回答问题,但提供有关此代码为何和/或如何回答问题的额外上下文可提高其长期价值。 不,不起作用。在 initState 中打印并构建,asyncInitState 中的代码在构建后执行 asyncInitState() 仅在构建小部件时执行。尝试在 initState 中使用 yourAsyncCalls().then((value)=>'Your code here')。【参考方案7】:您可以创建一个 async 方法并在您的 initState
中调用它@override
void initState()
super.initState();
asyncMethod(); ///initiate your method here
Future<void> asyncMethod async
await ///write your method body here
【讨论】:
【参考方案8】:根据https://pub.dev/packages/provider 的文档
initState()
super.initState();
Future.microtask(() =>
context.read<MyNotifier>(context).fetchSomething(someValue);
);
【讨论】:
【参考方案9】:示例代码:
@override
void initState()
super.initState();
asyncOperation().then((val)
setState(() );
print("success");
).catchError((error, stackTrace)
print("outer: $error");
);
//or
asyncOperation().whenComplete(()
setState(() );
print("success");
).catchError((error, stackTrace)
print("outer: $error");
);
Future<void> asyncOperation() async
await ... ;
【讨论】:
【参考方案10】:@override
void initState()
super.initState();
_userStorage.getCurrentUser().then((user)
setState(()
if (user.isAuthenticated)
Timer.run(()
redirectTo();
);
);
);
void redirectTo()
Navigator.push(context,
MaterialPageRoute(builder: (BuildContext context) => new ShopOrders()));
【讨论】:
【参考方案11】:initState() 和 build 不能是异步的,但在这些中,您可以调用异步函数而无需等待该函数
【讨论】:
【参考方案12】:由于加载或等待初始状态是(通常)一次性事件,FutureBuilder 似乎是一个不错的选择,因为它在异步方法上阻塞一次;其中异步方法可能是加载 json 配置、登录等。在堆栈中 [这里] 有一篇文章。(Flutter StreamBuilder vs FutureBuilder)
【讨论】:
【参考方案13】:这个怎么样?
@override
void initState()
//you are not allowed to add async modifier to initState
Future.delayed(Duration.zero,() async
//your async 'await' codes goes here
);
super.initState();
【讨论】:
【参考方案14】:我在 initState 中使用过计时器
Timer timer;
@override
void initState()
super.initState();
timer = new Timer.periodic(new Duration(seconds: 1), (Timer timer) async
await this.getUserVerificationInfo();
);
@override
void dispose()
super.dispose();
timer.cancel();
getUserVerificationInfo() async
await someAsyncFunc();
timer.cancle();
【讨论】:
【参考方案15】:又甜又短:
(() async
await your_method();
setState(() ....anything here);
)();
【讨论】:
永远不要在 initState 方法中调用 setState。因为如果你在 buidl 方法运行时调用它,它会破坏构建阶段【参考方案16】:我来到这里是因为我需要在程序启动时从 FTP 获取一些文件。我的项目是一个颤振桌面应用程序。主线程下载最后添加到 FTP 服务器的文件,解密并显示加密内容,该方法从 initState() 调用。我想在 GUI 出现后在后台下载所有其他文件。
上述方法均无效。构建 Isolate 相对复杂。
简单的方法是使用“计算”方法:
-
把从FTP下载所有文件的方法移出类。
使它成为一个带有 int 参数的 int 函数(我不使用 int 参数或结果)
从 initState() 方法调用它
这样,GUI 显示,程序在后台下载文件。
void initState()
super.initState();
_retrieveFileList(); // this gets the first file and displays it
compute(_backgroundDownloader, 0); // this gets all the other files so that they are available in the local directory
int _backgroundDownloader(int value)
var i = 0;
new Directory('data').createSync();
FTPClient ftpClient = FTPClient('www.guckguck.de',
user: 'maxmusterman', pass: 'maxmusterpasswort');
try
ftpClient.connect();
var directoryContent = ftpClient.listDirectoryContent();
// .. here, fileNames list is reconstructed from the directoryContent
for (i = 0; i < fileNames.length; i++)
var dirName = "";
if (Platform.isLinux)
dirName = 'data/';
else
dirName = r'data\';
var filePath = dirName + fileNames[i];
var myDataFile = new File(filePath);
if (!myDataFile.existsSync())
ftpClient.downloadFile(fileNames[i], File(filePath));
catch (err)
throw (err);
finally
ftpClient.disconnect();
return i;
【讨论】:
以上是关于有没有办法在 InitState 方法上加载异步数据?的主要内容,如果未能解决你的问题,请参考以下文章
Flutter应用打开时如何从sqlite数据库加载初始状态数据
Flutter:异步任务在“initState()”中无法按预期工作
如何在使用 pop 时重新加载或调用一些函数 initState()