有没有办法在 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数据库加载初始状态数据

颤振异步调用不会在 initState() 中异步运行

Flutter:异步任务在“initState()”中无法按预期工作

如何在使用 pop 时重新加载或调用一些函数 initState()

如何测试 initState() 中有异步调用的 Flutter 应用程序?

FLutter入门:异步加载组件FutureBuilder