Flutter 颤振';s令人费解的流行为:调用了setState,但没有重建小部件树

Flutter 颤振';s令人费解的流行为:调用了setState,但没有重建小部件树,flutter,stream,setstate,rebuild,Flutter,Stream,Setstate,Rebuild,为了掌握Flotter中streams的用法,我遵循了这里发现的有趣示例 ,它显示了如何从http调用中获取数据,并懒洋洋地填充照片对象/小部件(非常大)列表 整个过程归结为一个流和对它的订阅,它为流中的每个事件调用setState,同时向项列表中添加一个元素[为了调试,我放置了一个print(“ADD!”)语句,以确保调用按预期工作。确实如此] 在http调用之后,流被“填充”,http调用提供了原始演示数据的大列表。 为了查看flifter何时(重新)构建PhotoList小部件的主体,我在

为了掌握Flotter中streams的用法,我遵循了这里发现的有趣示例 ,它显示了如何从http调用中获取数据,并懒洋洋地填充照片对象/小部件(非常大)列表

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

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

颤振程序的全部代码如下:

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'];
}
导入“包装:颤振/材料.省道”;
导入“dart:async”;
导入“dart:convert”;
将“package:http/http.dart”导入为http;
void main()=>runApp(新的MyApp());
类MyApp扩展了无状态小部件{
@凌驾
小部件构建(构建上下文){
返回材料PP(
标题:“照片拖缆”,
主题:主题数据(
主样本:颜色。绿色,
),
主页:PhotoList(),
);
}
}
类PhotoList扩展了StatefulWidget{
@凌驾
PhotoListState createState()=>PhotoListState();
}
类PhotoListState扩展了状态{
流量控制器;
列表=[];
@凌驾
void initState(){
super.initState();
streamController=streamController.broadcast();
streamController.stream.listen((p)=>{
设置状态(){
增加(p);
打印(“添加!”);
})
});
负载(流量控制器);
}
加载(StreamController sc)异步{
字符串url=”https://jsonplaceholder.typicode.com/photos";
var client=newhttp.client();
var req=newhttp.Request('get',Uri.parse(url));
var streamdres=等待客户端发送(req);
溪流
.变换(utf8.解码器)
.transform(json.decoder)
.展开((e)=>e)
.map((map)=>Photo.fromJsonMap(map))
.管道(sc);
}
@凌驾
无效处置(){
super.dispose();
streamController?.close();
streamController=null;
}
@凌驾
小部件构建(构建上下文){
打印(“构建!!!”;
返回脚手架(
appBar:appBar(
标题:文本(“照片流”),
),
正文:中(
子项:ListView.builder(
滚动方向:轴水平,
itemBuilder:(BuildContext上下文,int index)=>\u makeElement(index),
),
),
);
}
Widget\u makeElement(int索引){
如果(索引>=列表长度){
返回null;
}
返回容器(
填充:所有边缘设置(5.0),
孩子:填充(
填充:仅限边缘设置(顶部:200.0),
子:列(
儿童:[
Image.network(列表[index].url,宽:150.0,高:150.0),
文本(列表[索引].标题),
],
),
));
}
}
班级照片{
最后的字符串标题;
最终字符串url;
照片:fromJsonMap(地图地图)
:title=地图['title'],
url=map['url'];
}
我希望每次调用setState(将一个元素添加到要显示的对象列表中)时,都会调用小部件树的新重建,打印结果如下:
加上
建造
加上
建造
... (等)

但这并没有发生。我看到的是:
建造
加上
加上


加上
建造

下面是我的问题:

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

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

最后,但并非最不重要的是——总体而言:

3) 如果http调用一次返回所有数据(正如它所做的那样),那么在触发异步/等待事件之后,使用流填充(长)项列表而不是直接填充列表有什么意义

[我是一个老算法学家,这些东西让我发疯:)]


我将永远感谢任何能为这些问题提供帮助的人谢谢。

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

可能有助于设置状态和构建感谢@user的链接。不过,据我所知,这个示例并不真正适合我的具体问题:我的示例中的所有关键函数都是异步的,因此所涉及的函数都会“退出”,并让事件循环处理挂起的事件。或者我遗漏了什么?这是因为所有事情都发生在一个帧中-
setState
可以调用100次,但仍然执行了一次重建-如果您想“看到”帧,请检查
debugPrintBeginFrameBanner
和/或
debugPrintEndFrameBanner
@pskink,谢谢您的回答和耐心。但你为什么说一切都发生在一个画面上?load(StreamController sc)函数的异步性质不允许事件循环。。。环流的构建不是使其事件异步流动吗?六羟甲基三聚氰胺六甲醚。。。如果不是这样,那么我就无法真正掌握流方法在读取http数据方面的优势。是否尝试设置
debugPrintBeginFrameBanner=true
?我很确定它发生在一个帧中,但最好再检查一遍