在InitState方法中是否有一种方法可以加载异步数据?

196

我希望在InitState方法中加载异步数据,我需要在build方法运行之前获取一些数据。我正在使用GoogleAuth代码,并且需要一直执行build方法,直到Stream运行。

我的initState方法是:

 @override
  void initState () {
    super.initState();
    _googleSignIn.onCurrentUserChanged.listen((GoogleSignInAccount account)     {
      setState(() {
        _currentUser = account;
      });
    });
    _googleSignIn.signInSilently();
  }

我将感激任何反馈。


4
StreamBuilder 是正确的解决方案。 - Rémi Rousselet
这段代码完全没有问题,不需要修改吗? - Jonah Williams
2
由于 initState() 只会被调用一次,那么在其中使用 setState() 有意义吗? - pragmateek
还要考虑使用FutyureBuilder,因为它在等待异步方法时只会阻塞一次;而流构建器模式是一系列不间断的未请求事件。 - Mark Parris
17个回答

148
你可以创建一个async方法,并在initState中调用它。
@override
void initState () {
  super.initState();
  WidgetsBinding.instance.addPostFrameCallback((_){
    _asyncMethod();
  });
        
}

_asyncMethod() async {
  _googleSignIn.onCurrentUserChanged.listen((GoogleSignInAccount account)     {
    setState(() {
      _currentUser = account;
    });
  });
  _googleSignIn.signInSilently();
}

7
除非他的呼叫是同步和即时的,否则不应更改任何内容。但是如果这样,就没有异步问题了。 - Rémi Rousselet
37
在这种情况下,“build”在“_asyncMethod”完成之前执行了! - BambinoUA
6
使用WidgetsBinding.instance.addPostFrameCallback的目的是什么,与@Nae的回答相比有何不同之处? - BIS Tech
5
在初始化状态时调用setState()是错误的,这会导致在页面加载后刷新页面,因为你正在initState中调用异步方法。解决方法是只使用FutureBuilder()。 - Mohsen Emami
2
@BISTech 使用 WidgetsBinding.instance.addPostFrameCallback 的原因是,由于异步方法调用了 setState(),而没有回调函数,则状态可能在 initState() 方法完成之前尝试进行更改,这是被禁止的。 - AutoM8R
显示剩余5条评论

109

目前使用.then符号似乎是有效的:

  // ...
  @override
  initState() {
    super.initState();
    myAsyncFunction
    // as suggested in the comment
    // .whenComplete() {
    // or
      .then((result) {
    print("result: $result");
    setState(() {});
    });
  }
  //...

42
如果您的异步函数不返回任何内容,请使用whenComplete代替。 - Ahmad Jamil Al Rasyid
3
个人而言,我尽可能喜欢使用流。但这只是为了演示目的而“可行”。我非常怀疑这是否是正确的方式。 - Nae
我收到了“未处理的异常:在dispose()之后调用了setState()”。 - Eradicatore
2
@Eradicatore,听起来像是在你的异步函数完成之前小部件被处理掉了。 - Nae
1
@ShouryaShikhar 流是构建小部件的事实标准方式,上面的答案只是一种异步加载的hacky方法,但管理小部件的生命周期等要困难得多。 - Nae
显示剩余3条评论

68

方法一: 您可以使用StreamBuilder实现此功能。这将在stream中的数据更改时运行builder方法。

以下是我一个示例项目中的代码片段:

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();
    // ....
  }

也许要根据他的流调整你的代码片段。 - Rémi Rousselet
@RémiRousselet编辑了答案并添加了您要求的信息。请检查是否正确。 - Thanthu
1
方法二简单易行,对我起了作用。谢谢。 - RJB
2
对于第二种方法,有没有一种方法可以在等待调用结束时触发setState()?我尝试了这个,但是我发现“mounted”现在为false,因此setState()会产生错误。@RémiRousselet - Eradicatore

31
在`initState`函数内创建匿名函数,代码如下:
@override
void initState() {
  super.initState();
  
  // Create anonymous function:
  () async {
    await _performYourTask();
    setState(() {
     // Update your UI with the desired changes. 
    });
  } ();
}

6
请勇敢地提出给予负投票背后的原因。 - iDecode
2
这与直接调用 _performYourTask(); 有何不同?将异步函数包装在另一个未等待的异步函数中有什么帮助? - Nae
1
在上述匿名函数中,您可以在_performYourTask完成后调用setState(这显然是无法直接在initState中完成的)。 - iDecode
因此,额外的包装是为了在所需的异步方法完成后排队setState调用。然后我建议在await之后添加该行。因为现在它似乎没有做任何不同的事情。 - Nae

8

前一个答案!!

在listen函数中设置布尔值(例如loaded)并将其设置为true,当loaded为true时,在build函数中返回您的数据,否则只需抛出CircularProgressIndicator。

编辑--我不建议在initState中调用setState方法。如果在async操作完成时调用setState时,小部件未安装,则会报告错误。我建议您使用after_layout包。

查看此答案以更好地了解在initState中使用setState:https://dev59.com/L1QJ5IYBdhLWcg3wqnwT#53373017

此帖子将让您了解何时应用程序完成构建方法的想法。因此,您可以等待异步方法在挂载小部件后setState:https://dev59.com/rVUK5IYBdhLWcg3w0Sx1#51273797


8
  @override
  void initState() {
    super.initState();
    asyncInitState(); // async is not allowed on initState() directly
  }

  void asyncInitState() async {
    await yourAsyncCalls();
  }

虽然这段代码可能回答了问题,但是提供关于为什么和/或如何回答问题的额外上下文可以提高其长期价值。 - β.εηοιτ.βε
3
不行,在initState和build中放置一个打印语句,asyncInitState中的代码会在build之后执行。 - chitgoks
asyncInitState() 只有在小部件构建时才会执行。尝试在initState中使用yourAsyncCalls().then((value)=>'Your code here')。 - NepoKale
1
这个可行,谢谢!其他解决方案也很可能有效,但是我很好奇为什么更复杂的解决方案,比如使用FutureBuilder,会更受欢迎。 - Jake Boomgaarden
欢迎。对我来说,越简单越好。 - Gorges

8

您可以创建一个 async 方法并在您的 initState 中调用它。

@override
  void initState() {
    super.initState();

    asyncMethod(); ///initiate your method here
  }

Future<void> asyncMethod async{
 
 await ///write your method body here
}

6

根据https://pub.dev/packages/provider上的文档:

initState() {
  super.initState();
  Future.microtask(() =>
    context.read<MyNotifier>(context).fetchSomething(someValue);
  );
}

4
这个怎么样?
@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();
}

4

initState()build不能是异步的;但在这些函数中,你可以调用一个异步的函数而不必等待该函数。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接