我想要实现的目标并不是什么疯狂的事情,但我连续几天都在拉头发。
我基本上想实现以下目标:一个长时间运行的进程进行互联网访问和处理具有多种状态的数据。我希望用户随时了解每个完成状态,因此我的有状态小部件显示一个进度条和一个显示状态名称的文本小部件。
我可以用这个大幅精简的版本来总结这一点,它可以在飞镖盘中完美运行:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String? _subState;
Stream<String?> longRunningProcess() async* {
// ... process logic ...
for (int i = 0; i < 10; i++) {
yield i.toString();
int a = 0;
for (int j = 0; j < 10000000; j++) {
a = a + 1;
}
await Future.delayed(Duration(seconds: 1)); // <<<=== Removing this breaks the realtime update of the widget
}
// Close the stream to signal completion
}
@override
void initState() {
longRunningProcess()
.listen((subState) => setState(() => _subState = subState));
super.initState();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: Text('state = $_subState!'),
),
),
);
}
}
正如您所看到的,就像在我的实际代码的最新版本中一样,我想出了一个利用屈服的版本:每个状态都会产生一个字符串,该字符串表示状态正在做什么,而侦听器只需
setState()
它。
如果您在 Dart pad 中运行,您会看到所有状态都完美地实时显示。
但我的代码中情况并非如此。我希望您给我建议并就我可以研究的解决方案提出建议。
有一件事:如果您消除延迟,您将面临与我相同的情况,因为小部件仅显示最后一步,就好像小部件永远没有机会触发构建一样。
在我的实际代码中,我通过调试器看到,我的
setState()
按预期运行,即使我延迟对渲染系统进行更改以触发构建,我也没有明白。
这是我的监听器代码:
_subscription = future.listen(
(subState) async {
debugPrint("GasStationDownloaderView got sub state: $subState");
setState(() => _subState = subState); // <<<== the debugger stops on this line with no problem and I can see the debuPrint prints the various states.
await Future.delayed(Duration(seconds: 1));
},
onDone: () => Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => AppBottomNavigationBarController(),
),
),
);
按照@pskink的建议,我将
setState()
替换为StreamBuilder()
,现在可以使用了。
这是最终代码:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
late final Sream<String?> _stream;
StreamSubscription<String?>? _subscription;
Stream<String?> longRunningProcess() async* {
// ... process logic ...
for (int i = 0; i < 10; i++) {
yield i.toString();
int a = 0;
for (int j = 0; j < 10000000; j++) {
a = a + 1;
}
await Future.delayed(Duration(seconds: 1)); // <<<=== Removing this breaks the realtime update of the widget
}
// Close the stream to signal completion
}
@override
void initState() {
// Make our stream a broadcast stream so that I can have more than one listener.
_stream = longRunningProcess().asBroadcastStream();
// Keep posted for the stream to complete
_subscription = _stream
.listen(
null,
onDone: () {
// It's all good
},
);
super.initState();
}
@override
void dispose() {
_subscription?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: StreamBuilder(
stream: _stream,
builder: (context, snapshot) {
final subState = snapshot.data;
return Text('state = $subState!');
},
),
),
),
);
}
}