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