dart - 发生更改时如何在 Flutter 中重建小部件
问题描述
编辑:我已经编辑了下面的代码,以使用获取数据的方法以及构建火车估计的小部件(沿途用"API_URL"
和替换任何 API 信息"API_STOP_ID"
)。我希望这能更好地帮助我们找出问题所在!我非常感谢任何人可以提供的任何信息——我一直在为这个项目努力工作!再次感谢大家!
原始帖子: 我有一个 ListTiles 的 ListView,每个都有一个尾随小部件,该小部件在新的 Text 小部件中构建火车到达估计值。这些尾随小部件每五秒更新一次(由打印语句证明)。当应用程序从火车的 API 获取数据时,它会显示一个“无数据”文本小部件,该小部件由 _buildEstimatesNull() 构建。
但是,问题是即使应用程序完成获取数据并且_isLoading = false
(通过打印语句证明),仍然显示“无数据”。尽管如此,即使解决了这个问题,火车的估计也会很快过时,因为尾随的小部件每五秒就会自行更新一次,但这不会反映在实际的应用程序中,因为小部件是在页面加载时构建的。因此,每当它们获取新信息时,我需要一种方法来重建那些尾随小部件。
有没有办法让 Flutter 每五秒自动重建 ListTile 的尾随小部件(或者每当 _buildEstimatesS1 更新/尾随小部件的内部更新时)?
class ShuttleApp extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return new ShuttleState();
}
}
class ShuttleState extends State<ShuttleApp> {
@override
Widget build(BuildContext context) {
return new MaterialApp(
home: new HomeScreen(),
);
}
}
class HomeScreen extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return new HomeState();
}
}
class HomeState extends State<HomeScreen> {
var _isLoading = true;
void initState() {
super.initState();
_fetchData();
const fiveSec = const Duration(seconds: 5);
new Timer.periodic(fiveSec, (Timer t) {
_fetchData();
});
}
var arrivalsList = new List<ArrivalEstimates>();
_fetchData() async {
arrivalsList.clear();
stopsList.clear();
final url = "API_URL";
print("Fetching: " + url);
final response = await http.get(url);
final busesJson = json.decode(response.body);
if (busesJson["service_id"] == null) {
globals.serviceActive = false;
} else {
busesJson["ResultSet"]["Result"].forEach((busJson) {
if (busJson["arrival_estimates"] != null) {
busJson["arrival_estimates"].forEach((arrivalJson) {
globals.serviceActive = true;
final arrivalEstimate = new ArrivalEstimates(
arrivalJson["route_id"], arrivalJson["arrival_at"], arrivalJson["stop_id"]
);
arrivalsList.add(arrivalEstimate);
});
}
});
}
setState(() {
_isLoading = false;
});
}
Widget _buildEstimateNull() {
return new Container(
child: new Center(
child: new Text("..."),
),
);
}
Widget _buildEstimateS1() {
if (globals.serviceActive == false) {
print('serviceNotActive');
_buildEstimateNull();
} else {
final String translocStopId = "API_STOP_ID";
final estimateMatches = new List<String>();
arrivalsList.forEach((arrival) {
if (arrival.stopId == translocStopId) {
estimateMatches.add(arrival.arrivalAt);
}
});
estimateMatches.sort();
if (estimateMatches.length == 0) {
print("zero");
return _buildEstimateNull();
} else {
return new Container(
child: new Center(
child: new Text(estimateMatches[0]),
),
);
}
}
}
@override
Widget build(BuildContext context) {
return new Scaffold(
backgroundColor: const Color(0xFF171717),
appBar: new AppBar(),
body: new DefaultTextStyle(
style: new TextStyle(color: const Color(0xFFaaaaaa),),
child: new ListView(
children: <Widget>[
new ListTile(
title: new Text('S1: Forest Hills',
style: new TextStyle(fontWeight: FontWeight.w500, fontSize: 20.0)),
subtitle: new Text('Orange Line'),
contentPadding: new EdgeInsets.symmetric(vertical: 16.0, horizontal: 16.0),
trailing: _isLoading ? _buildEstimateNull() : _buildEstimateS1(),
),
],
),
)
);
}
class ArrivalEstimates {
final String routeId;
final String arrivalAt;
final String stopId;
ArrivalEstimates(this.routeId, this.arrivalAt, this.stopId);
}
非常感谢您提供的任何帮助!我真的超级感激!:)
解决方案
有几种方法可以解决这个问题。然而,在不看更多代码的情况下判断发生了什么有点困难 - 特别是您如何获取数据以及您正在使用它做什么。但我想无论如何我都可以给你一个足够的答案。
这样做的简单方法是:
- 有一个 StatefulWidget 来跟踪列表中所有项目的构建估计。它应该从您的 API 请求数据,获取结果,然后调用
setState(() => this.listData = data);
. 对 setState 的调用告诉小部件它需要重建。 - 列表中的每个项目都有一个 StatefulWidget。他们每个人都会每 5 秒执行一次 API 请求,获取结果,然后每个人都会调用
setState(() => this.itemData = data);
. 这意味着对 API 等的多次调用。
#1 的优点是您可以批处理 API 调用,而 #2 的优点是您的构建整体变化较小(尽管颤振的工作方式,这将是非常小的)......所以我可能会选择 # 1 如果可能的话。
但是,有更好的方法来做到这一点!
更好的方法是使用某种 API 管理器(或任何您想调用的名称)来处理与您的 API 的通信。它可能会在您的小部件树中处于较高位置,并且会以您想要的任何逻辑启动/停止。根据小部件树的高度,您可以将它传递给每个子元素,或者更有可能将其保存在 InheritedWidget 中,然后可以使用它从每个列表元素或整个列表中检索它。
API 管理器将提供各种流——根据您的 API,要么带有一堆命名字段/方法,要么带有 getStream(id) 类型的结构。
然后,在您的各种列表元素中,您将使用StreamBuilder小部件根据数据构建每个元素 - 通过使用 StreamBuilder,您将获得一个 ConnectionState 对象,该对象让您知道流是否已收到任何数据,因此您可以选择显示一个 isLoading 类型的小部件,而不是显示数据的小部件。
通过使用这种更高级的方法,您可以获得:
- 可维护性
- 如果您的 API 发生变化,您只需更改 API 管理器
- 您可以编写更好的测试,因为 API 交互和 UI 交互是分开的
- 可扩展性
- 如果您稍后使用推送通知进行更新,而不是每 5 秒 ping 一次服务器,则可以将其合并到 API 管理器中,以便它可以简单地更新流而无需接触 UI
编辑:根据 OP 的评论,他们已经或多或少地实施了第一个建议。但是,代码存在一些问题。我将在下面列出它们,并且我已经发布了代码并进行了一些更改。
arrivalsList
每次新构建完成时都应该更换,而不是简单地进行更改。这是因为 dart 会比较列表,如果找到相同的列表,它不一定会比较所有元素。此外,虽然在函数中间更改它不一定会导致问题,但通常最好使用局部变量,然后在最后更改值。请注意,该成员实际上是在 setState 中设置的。- 如果 serviceActive == false,则从
return _buildEstimateNull();
.
这是代码:
class HomeState extends State<HomeScreen> {
var _isLoading = true;
void initState() {
super.initState();
_fetchData();
const fiveSec = const Duration(seconds: 5);
new Timer.periodic(fiveSec, (Timer t) {
_fetchData();
});
}
var arrivalsList = new List<ArrivalEstimates>();
_fetchData() async {
var arrivalsList = new List<ArrivalEstimates>(); // *********** #1
stopsList.clear();
final url = "API_URL";
print("Fetching: " + url);
final response = await http.get(url);
final busesJson = json.decode(response.body);
if (busesJson["service_id"] == null) {
print("no service id");
globals.serviceActive = false;
} else {
busesJson["ResultSet"]["Result"].forEach((busJson) {
if (busJson["arrival_estimates"] != null) {
busJson["arrival_estimates"].forEach((arrivalJson) {
globals.serviceActive = true;
final arrivalEstimate = new ArrivalEstimates(
arrivalJson["route_id"], arrivalJson["arrival_at"], arrivalJson["stop_id"]
);
arrivalsList.add(arrivalEstimate);
});
}
});
}
setState(() {
_isLoading = false;
this.arrivalsList = arrivalsList; // *********** #1
});
}
Widget _buildEstimateNull() {
return new Container(
child: new Center(
child: new Text("..."),
),
);
}
Widget _buildEstimateS1() {
if (globals.serviceActive == false) {
print('serviceNotActive');
return _buildEstimateNull(); // ************ #2
} else {
final String translocStopId = "API_STOP_ID";
final estimateMatches = new List<String>();
print("arrivalsList length: ${arrivalsList.length}");
arrivalsList.forEach((arrival) {
if (arrival.stopId == translocStopId) {
print("Estimate match found: ${arrival.stopId}");
estimateMatches.add(arrival.arrivalAt);
}
});
estimateMatches.sort();
if (estimateMatches.length == 0) {
print("zero");
return _buildEstimateNull();
} else {
return new Container(
child: new Center(
child: new Text(estimateMatches[0]),
),
);
}
}
}
@override
Widget build(BuildContext context) {
return new Scaffold(
backgroundColor: const Color(0xFF171717),
appBar: new AppBar(),
body: new DefaultTextStyle(
style: new TextStyle(color: const Color(0xFFaaaaaa),),
child: new ListView(
children: <Widget>[
new ListTile(
title: new Text('S1: Forest Hills',
style: new TextStyle(fontWeight: FontWeight.w500, fontSize: 20.0)),
subtitle: new Text('Orange Line'),
contentPadding: new EdgeInsets.symmetric(vertical: 16.0, horizontal: 16.0),
trailing: _isLoading ? _buildEstimateNull() : _buildEstimateS1(),
),
],
),
)
);
}
推荐阅读
- .net - 为什么在 .NET4.6 中很少有分析器规则将其有效严重性设置为抑制
- c++ - 如何正确创建合并排序函数?
- git - Git ssh url 到特定文件夹
- google-cloud-platform - 是否可以在本地模拟 Google Cloud Task?
- windows - Powershell:如何在“;”之后获得数字的平均值 象征?(来自一个txt文件)
- javascript - 如何在节点中创建 Twilio 视频连接?
- admob - 仅仅请求应用透明度权限是否足以确保谷歌移动广告符合 iOS14 标准?
- amazon-web-services - AWS ec2 userdata ssl验证错误..唯一
- angular - 角度单元测试:属性订阅没有访问类型获取
- apache-beam - 非常小的 BEAM pCollection 没有被刷新到输出文件