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(StreamControllerdebugPrintBeginFrameBanner = true
?我很确定它在一帧中发生,但最好仔细检查一下
【参考方案1】:
根据@pskink 的建议,示例中使用的与流相关的所有活动都在单个框架中进行管理。 这在某种程度上解释了为什么 setState 不会触发 PhotoListState 元素的构建方法。因此,问题 1 现在有了答案。
【讨论】:
以上是关于Flutter 令人费解的流行为:setState 调用但不重建 widget 树的主要内容,如果未能解决你的问题,请参考以下文章
iPhone:iOS 6 和 5.1 之间 cellForRowAtIndexPath 行为的令人费解的差异