Flutter的FutureBuilder进行了多次重建

时间:2020-04-18 00:20:35

标签: flutter future flutter-provider

我对Flutter还是比较陌生,目前正与FutureBuilders苦苦挣扎。 我已经读过Remi在thread上的回答,该回答基本上说明了Flutter的哲学,即小部件构建应该是幂等的。我从理论上得到了原理,但是实际上如何工作?

请考虑以下代码段:

return Scaffold(
  body: SafeArea(
    child: Stack(
      children: [
        Consumer<DataPresenter>(
            builder: (context, presenter, _) {
              return DefaultFutureBuilder<List<Data>>(
                    future: presenter.data(),
                    builder: (context, data) {
                      // bla bla 
                    }
                );
            }
        ),
        // More widgets
      ],
    ),
  ),
);

}

这是我的DefaultFutureBuilder

class DefaultFutureBuilder<T> extends StatelessWidget {
  final Future<T> future;
  final Widget Function(BuildContext, T data) builder;
  final Widget Function(BuildContext) errorBuilder;

  DefaultFutureBuilder({
    @required this.future,
    @required this.builder,
    this.errorBuilder,
  });

  @override
  Widget build(BuildContext context) {
    var errBuilder = errorBuilder ?? _buildDefaultErrorScreen;

    return FutureBuilder<T>(
      future: future,
      builder: (context, snapshot) {
        var indicator = Center(child: CircularProgressIndicator());

        if (snapshot.connectionState == ConnectionState.done) {
          if (snapshot.hasData)
            return builder(context, snapshot.data);
          else if (snapshot.hasError)
            return errBuilder(context);
        }

        return indicator;
      },
    );
  }

  Widget _buildDefaultErrorScreen(BuildContext context) {
    // default
  }
}


现在我知道对presenter.data()的调用(获取一些异步数据)不是洁净的。但是,我要做,当演示者通知消费者时,我想重新获取数据,与此同时,由于框架的恶作剧,Flutter重建我的小部件树时,我不想获取数据。

我最初的想法是构建演示者,使其仅在当前没有任何数据时才提取数据。然后,我可以通过其他方法将数据设置为null,并通知侦听器以重建受影响的小部件子树。
像这样:

class DataPresenter extends ChangeNotifier {
  // fields, services, bla bla
  List<Data> _data; 

  Future<List<Data>> data() async {
    if (_data == null) {
      var response = await _service.fetchMyStuff(params: params);
      _data = response.data;
    }

    return _data;
  }
}

此解决方案的问题在于,当我第一次获取数据时发生的任何重建都会引起另一个请求。

如果有人能指出我没有得到框架/架构的哪一部分,我将不胜感激。

3 个答案:

答案 0 :(得分:3)

我认为这可能会解决您的问题,我对此问题感到非常烦恼,您所做的任何事情都会重新构建您的应用。所以他们解决问题的方式是记住我的未来。现在,这可能对您不起作用。如果它将Consumer重建视为新的未来,那么您会很好,因为如果FutureBuilder这样做,Consumer将会重建,如果我理解正确的话,这就是您想要的。这就是你的工作。

//Initialize at top of class
final AsyncMemoizer _memoizer = AsyncMemoizer(); 


 Future<void> _someFuture() {
    return this._memoizer.runOnce(() async {
     //Do your async work
    });
  }

然后_someFuture()将成为您传递给FutureBuilder的对象。这是一篇关于此问题的好文章。

Flutter: My FutureBuilder Keeps Firing!

答案 1 :(得分:0)

我会建议一个没有futureBuilder的解决方案

您在首次构建无状态小组件后获取数据

WidgetsBinding.instance.addPostFrameCallback((_) => Provider.of<DataPresenter>(context, listen: false).fetchdata());

您的_data列表中的值为nulllenght==0

class DataPresenter extends ChangeNotifier {
  // fields, services, bla bla
  List<Data> _data; 
  // getter for the Consumer
  List<Data> get data => _data;


  Future<List<Data>> fetchdata() async {

     _data = await _service.fetchMyStuff(params: params);
     if (_data == null) 
       _data = new List(); //difference between null and length==0

     //rebuild Consumer
     notifyListeners();

  }
}

没有FutureBilder的页面

class ProvPage extends StatelessWidget {

  @override
  Widget build(BuildContext context) {

    // calls after the widget ist build
    WidgetsBinding.instance.addPostFrameCallback((_) => Provider.of<TodoListModel>(context, listen: false).refreshTasks());

    return MultiProvider(
        providers: [
              ChangeNotifierProvider(create: (context) => DataPresenter()),
        ],
        child: Scaffold(
          body: SafeArea(
            child: Stack(
              children: [
                Consumer<DataPresenter>(
                    builder: (context, presenter, _) {

                       //get current Values vom the DataPresenter._data
                       List<Data> currData = presenter.data;

                       // firstime fetch ??
                       if(currData == null)
                         return CircularProgressIndicator();

                       // DataPresenter.getData is finished
                       return new RefreshIndicator(
                           onRefresh: () => presenter.fetchdata(),
                           child: new Text("length " + currData.length.toString())
                       );

                    },
                ),
                // More widgets
              ],
            ),
          ),
        ),
    );
}
}

答案 2 :(得分:0)

如果您想重建提供商,则可以使用

switch -File (gc <yourfile>) -Regex {
    '--------' {$skipnext=$true}
    '^C:\\test' {if ($skipnext) {$skipnext = $false} else {$_}}
    }
}

此方法对于“拉动刷新”或“重试错误”之类的功能很有用,可随时重新启动特定的提供程序。

可从以下网站获得文档: https://riverpod.dev/docs/concepts/combining_providers#faq https://pub.dev/documentation/riverpod/latest/all/ProviderContainer/refresh.html https://pub.dev/documentation/flutter_riverpod/latest/all/BuildContextX/refresh.html