Flutter 令人费解的流行为:setState 调用但不重建 widget 树

Posted

技术标签:

【中文标题】Flutter 令人费解的流行为:setState 调用但不重建 widget 树【英文标题】:Flutter's puzzling stream behaviour: setState called but not rebuilding widget tree 【发布时间】:2020-08-02 00:46:35 【问题描述】:

为了掌握 Flutter 中流的用法,我按照这里找到的有趣示例进行了操作 https://github.com/tensor-programming/flutter_streams,它展示了如何从 http 调用中获取数据,并懒惰地填充一个(非常大的)照片对象/小部件列表。

整个事情归结为一个流和对它的订阅,它为流中的每个事件调用 setState,同时将一个元素添加到项目列表中 [为了调试,我放了一个 print("ADD!") 语句以确保呼叫按预期工作。确实如此]。

在 http 调用之后,流被“填充”,它提供了原始演示数据的大列表。 为了查看 Flutter 何时(重新)构建 PhotoList 小部件的主体,我在返回 Scaffold 之前放置了一个不错的 print("BUILD!!!")

flutter程序的整个代码是这样的:

import 'package:flutter/material.dart';
import 'dart:async';
import 'dart:convert';

import 'package:http/http.dart' as http;

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return MaterialApp(
      title: 'Photo Streamer',
      theme: ThemeData(
        primarySwatch: Colors.green,
      ),
      home: PhotoList(),
    );
  


class PhotoList extends StatefulWidget 
  @override
  PhotoListState createState() => PhotoListState();


class PhotoListState extends State<PhotoList> 
  StreamController<Photo> streamController;
  List<Photo> list = [];

  @override
  void initState() 
    super.initState();
    streamController = StreamController.broadcast();

    streamController.stream.listen((p) => 
          setState(() 
            list.add(p);
            print("ADD!");
          )
        );

    load(streamController);
  

  load(StreamController<Photo> sc) async 
    String url = "https://jsonplaceholder.typicode.com/photos";
    var client = new http.Client();

    var req = new http.Request('get', Uri.parse(url));

    var streamedRes = await client.send(req);

    streamedRes.stream
        .transform(utf8.decoder)
        .transform(json.decoder)
        .expand((e) => e)
        .map((map) => Photo.fromJsonMap(map))
        .pipe(sc);
  

  @override
  void dispose() 
    super.dispose();
    streamController?.close();
    streamController = null;
  

  @override
  Widget build(BuildContext context) 
    print("BUILD!!!");
    return Scaffold(
      appBar: AppBar(
        title: Text("Photo Streams"),
      ),
      body: Center(
        child: ListView.builder(
          scrollDirection: Axis.horizontal,
          itemBuilder: (BuildContext context, int index) => _makeElement(index),
        ),
      ),
    );
  

  Widget _makeElement(int index) 
    if (index >= list.length) 
      return null;
    

    return Container(
        padding: EdgeInsets.all(5.0),
        child: Padding(
          padding: EdgeInsets.only(top: 200.0),
          child: Column(
            children: <Widget>[
              Image.network(list[index].url, width: 150.0, height: 150.0),
              Text(list[index].title),
            ],
          ),
        ));
  


class Photo 
  final String title;
  final String url;

  Photo.fromJsonMap(Map map)
      : title = map['title'],
        url = map['url'];

我期望每次调用 setState(将元素添加到要显示的对象列表中),都会调用一个新的小部件树重建,打印看起来像: 添加! 建立!!! 添加! 建立!!! ...(等等)

但这不会发生。我看到的是: 建立!!!! 添加! 添加! ... ... 添加! 建立!!!!

我的问题来了:

1) 为什么 Flutter 只重建小部件两次,即使每次将项目添加到列表对象时都会调用 setState?

2)如果整个列表在重绘之前一次性填充,那么使用流有什么意义?

最后但并非最不重要的——一般来说——:

3)如果http调用一次返回所有数据(因为它确实如此),那么使用流填充(长)项目列表而不是直接填充列表有什么意义 - 在 async/ 之后await 事件已被触发?

[我是一个老算法专家,这些事情让我发疯:)].

我将永远感谢任何能够阐明这些问题的人!谢谢。

【问题讨论】:

***.com/questions/59555999/… 可能有助于 setState 和构建 感谢@user 提供链接。不过,据我所知,该示例并不真正适合我的具体问题:我示例中的所有关键函数都是异步的,因此所涉及的函数会“退出”并让事件循环处理未决事件。还是我错过了什么? 这是因为一切都发生在一个帧中 - setState 可以被调用 100 次,但仍然执行一次重建 - 如果你想“看到”帧检查 debugPrintBeginFrameBanner 和/或 @ 987654328@ @pskink,感谢您的回答和耐心等待。但是你为什么说一切都发生在一个框架中? load(StreamController sc) 函数的异步性质不允许事件循环...循环吗?流不是为了使其事件异步流动而构建的吗?嗯……如果不是这样的话,那我就无法真正掌握流方式读取http数据的优势了。 试图设置debugPrintBeginFrameBanner = true?我很确定它在一帧中发生,但最好仔细检查一下 【参考方案1】:

根据@pskink 的建议,示例中使用的与流相关的所有活动都在单个框架中进行管理。 这在某种程度上解释了为什么 setState 不会触发 PhotoListState 元素的构建方法。因此,问题 1 现在有了答案。

【讨论】:

以上是关于Flutter 令人费解的流行为:setState 调用但不重建 widget 树的主要内容,如果未能解决你的问题,请参考以下文章

令人费解的作用域行为

令人费解的 javascript 数组行为

来自 QFontDatabase 的令人费解的行为

iPhone:iOS 6 和 5.1 之间 cellForRowAtIndexPath 行为的令人费解的差异

Winsock - 10038 错误 - Win2K3 服务器 - 令人费解的行为

使用Tensorflow后端的Keras LSTM RNN中令人费解的训练损失与纪元...行为的任何原因