【问题标题】:Usage of FutureBuilder with setState将 FutureBuilder 与 setState 一起使用
【发布时间】:2019-01-31 22:09:13
【问题描述】:

如何正确使用FutureBuildersetState?例如,当我创建一个有状态小部件时,它开始加载数据(FutureBuilder),然后我应该用新数据更新列表,所以我使用 setState,但它开始无限循环(因为我再次重建小部件),任何解决方案?

class FeedListState extends State<FeedList> {

  Future<Null> updateList() async {
    await widget.feeds.update();
    setState(() {
      widget.items = widget.feeds.getList();
    });
    //widget.items = widget.feeds.getList();
  }

  @override
  Widget build(BuildContext context) {
    return new FutureBuilder<Null>(
      future: updateList(),
      builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
        switch (snapshot.connectionState) {
          case ConnectionState.waiting:
            return new Center(
              child: new CircularProgressIndicator(),
            );
          default:
            if (snapshot.hasError)
              return new Text('Error: ${snapshot.error}');
            else
              return new Scrollbar(
                child: new RefreshIndicator(
                  child: ListView.builder(
                    physics:
                        const AlwaysScrollableScrollPhysics(), //Even if zero elements to update scroll
                    itemCount: widget.items.length,
                    itemBuilder: (context, index) {
                      return FeedListItem(widget.items[index]);
                    },
                  ),
                  onRefresh: updateList,
                ),
              );
        }
      },
    );
  }
}

【问题讨论】:

  • 使用FutureBuilder的全部意义在于自己调用setState
  • 我明白,但我想以某种方式在使用 FutureBuilder 首次启动后更新数据(来自互联网)

标签: dart flutter


【解决方案1】:

确实,它会循环到无穷大,因为每当调用build 时,也会调用updateList 并返回一个全新的未来。

您必须保持build 的纯净。它应该只是读取和组合变量和属性,但绝不会产生任何副作用!


另一个注意事项:StatefulWidget 子类的所有字段都必须是最终字段(widget.items = ... 不好)。更改的状态必须存储在State 对象中。

在这种情况下,您可以将结果(列表的数据)存储在将来本身,不需要单独的字段。从 future 调用 setState 甚至是危险的,因为 future 可能在处理状态后完成,并且会抛出错误。

下面是一些考虑到所有这些因素的更新代码:

class FeedListState extends State<FeedList> {
  // no idea how you named your data class...
  Future<List<ItemData>> _listFuture;

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

    // initial load
    _listFuture = updateAndGetList();
  }

  void refreshList() {
    // reload
    setState(() {
      _listFuture = updateAndGetList();
    });
  }

  Future<List<ItemData>> updateAndGetList() async {
    await widget.feeds.update();

    // return the list here
    return widget.feeds.getList();
  }

  @override
  Widget build(BuildContext context) {
    return new FutureBuilder<List<ItemData>>(
      future: _listFuture,
      builder: (BuildContext context, AsyncSnapshot<List<ItemData>> snapshot) {
        if (snapshot.connectionState == ConnectionState.waiting) {
          return new Center(
            child: new CircularProgressIndicator(),
          );
        } else if (snapshot.hasError) {
          return new Text('Error: ${snapshot.error}');
        } else {
          final items = snapshot.data ?? <ItemData>[]; // handle the case that data is null

          return new Scrollbar(
            child: new RefreshIndicator(
              child: ListView.builder(
                physics: const AlwaysScrollableScrollPhysics(), //Even if zero elements to update scroll
                itemCount: items.length,
                itemBuilder: (context, index) {
                  return FeedListItem(items[index]);
                },
              ),
              onRefresh: refreshList,
            ),
          );
        }
      },
    );
  }
}

【讨论】:

  • 你的评论,你真的帮助了我,特别是在 State 类中声明变量!
  • 很好的例子。不过我有一个小小的疑问。如果我们想用当前列表追加更新列表怎么办?当前示例用新数据替换所有行,我希望它附加现有行。
  • 也许会问一个新问题。您可能希望使用Stream 或rx Observable 而不是Future,并使用StreamSinkSubject 来触发加载。
  • 但是是什么让 Futurebuilder 一直在无意中开火。?因为我的未来建造者正在这样做。
  • 一个小部件可能会被构建多次,这就是为什么 build() 必须尽可能薄的原因。 InitState 是初始化的理想场所,特别是处理数据库读取。
【解决方案2】:

使用可以在 Future Builders 或 Stream Builder 中使用 setState() 的 SchedulerBinding,

 SchedulerBinding.instance
                .addPostFrameCallback((_) => setState(() {
              isServiceError = false;
              isDataFetched = true;
            }));

【讨论】:

  • 这仍然会造成无限循环问题
【解决方案3】:

屏幕截图(Null Safe):


代码:

使用FutureBuilder 时不需要setState

class MyPage extends StatefulWidget {
  @override
  State<MyPage> createState() => _MyPageState();
}

class _MyPageState extends State<MyPage> {
  // Declare a variable.
  late final Future<int> _future;

  @override
  void initState() {
    super.initState();
    _future = _calculate(); // Assign your Future to it. 
  }

  // This is your actual Future. 
  Future<int> _calculate() => Future.delayed(Duration(seconds: 3), () => 42);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: FutureBuilder<int>(
        future: _future, // Use your variable here (not the actual Future)
        builder: (_, snapshot) {
          if (snapshot.hasData) return Text('Value = ${snapshot.data!}');
          return Text('Loading...');
        },
      ),
    );
  }
}

【讨论】:

    猜你喜欢
    • 2021-10-06
    • 1970-01-01
    • 2020-06-07
    • 1970-01-01
    • 2019-01-03
    • 2020-09-30
    • 1970-01-01
    • 2019-09-27
    • 1970-01-01
    相关资源
    最近更新 更多