英文:
build() is called but display not updated until hot reload
问题
I have an async stream which generates consecutive integers every second:
Stream<int> f() async* {
for (int i = 0; i < 100; ++i) {
await Future.delayed(Duration(seconds: 1));
yield i;
}
}
I have a state List<Widget> l = [];
and want to append a Card
to it every second by using the stream:
@override
void initState() {
super.initState();
f().listen((d) {
setState(
() => this.l.add(Card(child: ListTile(title: Text(d.toString())))));
});
}
And here's the build()
method:
@override
Widget build(BuildContext context) {
print('build(): ${this.l.length}');
return ListView(children: this.l);
}
When I run the program via flutter run -d macos
(i.e. macOS desktop app), display isn't updated at all although build()
is called and this.l
is updated every second. The console output is:
flutter: build(): 0
flutter: build(): 1
flutter: build(): 2
flutter: build(): 3
flutter: build(): 4
flutter: build(): 5
...
If I press r key to perform hot reload, display is updated.
Why?
英文:
I have an async stream which generates consecutive integers every second:
Stream<int> f() async* {
for (int i = 0; i < 100; ++i) {
await Future.delayed(Duration(seconds: 1));
yield i;
}
}
I have a state List<Widget> l = [];
and want to append a Card
to it every second by using the stream.
@override
void initState() {
super.initState();
f().listen((d) {
setState(
() => this.l.add(Card(child: ListTile(title: Text(d.toString())))));
});
}
And here's the build()
method:
@override
Widget build(BuildContext context) {
print('build(): ${this.l.length}');
return ListView(children: this.l);
}
When I run the program via flutter run -d macos
(i.e. macOS desktop app), display isn't updated at all although build()
is called and this.l
is updated every second. The console output is
flutter: build(): 0
flutter: build(): 1
flutter: build(): 2
flutter: build(): 3
flutter: build(): 4
flutter: build(): 5
...
If I press <kbd>r</kbd> key to perform hot reload, display is updated.
Why?
How to reproduce:
-
flutter run -d macos
to start the application. -
Even if
build()
is called every second, display remains fully black. -
If you press <kbd>r</kbd> key to perform hot reload, display is updated.
Full code here:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
brightness: Brightness.dark,
primaryColor: Colors.blueGrey,
useMaterial3: true),
home: Scaffold(body: W()),
debugShowCheckedModeBanner: false,
);
}
}
class W extends StatefulWidget {
const W({
super.key,
});
@override
State<W> createState() => _WState();
}
class _WState extends State<W> {
List<Widget> l = [];
@override
void initState() {
super.initState();
Stream<int> f() async* {
for (int i = 0; i < 100; ++i) {
await Future.delayed(Duration(seconds: 1));
yield i;
}
}
f().listen((d) {
setState(
() => this.l.add(Card(child: ListTile(title: Text(d.toString())))));
});
}
@override
Widget build(BuildContext context) {
print('build(): ${this.l.length}');
return ListView(children: this.l);
}
}
答案1
得分: 1
尝试将 ListView(children: this.l)
改为以下之一:
-
ListView(children: List.of(this.l))
-
或者
ListView.builder(itemCount: l.length, itemBuilder: (_, i) => l[i])
这是因为 ListView
小部件。ListView
就像一个静态小部件,它只接收起始参数列表 l
,即使你调用 setState
,它也不会更新。
更严格的解释
> 与框架中的其他小部件一样,此小部件期望子项列表在传递到此处后不会被更改。有关更多详细信息,请参阅 SliverChildListDelegate.children 的文档。
而嵌入的链接指出
> 此外,在Flutter中,Widget
是不可变的,因此直接修改子项,比如 someWidget.children.add(...)
或将原始列表值的引用传递给子项参数将导致不正确的行为。每当修改子项列表时,应提供一个新的列表对象。
英文:
Try to change ListView(children: this.l)
to
-
ListView(children: List.of(this.l))
-
or
ListView.builder(itemCount: l.length, itemBuilder: (_, i) => l[i])
It's because ListView
widget. ListView
likes a static widget, it receives only the beginning argument list l
and does not update even you call setState.
More Strict Explanation
The official document of ListView()
constructor says
> Like other widgets in the framework, this widget expects that the children list will not be mutated after it has been passed in here. See the documentation at SliverChildListDelegate.children for more details.
and the embedded link says
> Also, a Widget
in Flutter is immutable, so directly modifying the children such as someWidget.children.add(...)
or passing a reference of the original list value to the children parameter will result in incorrect behaviors. Whenever the children list is modified, a new list object should be provided.
答案2
得分: 0
请使用构建器(builder)代替以下代码:
import 'dart:async';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
brightness: Brightness.dark,
primaryColor: Colors.blueGrey,
useMaterial3: true,
),
home: Scaffold(body: W()),
debugShowCheckedModeBanner: false,
);
}
}
class W extends StatefulWidget {
const W({Key key}) : super(key: key);
@override
State<W> createState() => _WState();
}
class _WState extends State<W> {
StreamController<int> streamController;
List<String> list = []; // 将字符串更改为任何数据对象
@override
void initState() {
super.initState();
streamController = StreamController<int>();
Stream<int> f() async* {
for (int i = 0; i < 100; ++i) {
await Future.delayed(Duration(seconds: 1));
yield i;
}
}
f().listen((d) {
setState(() {
list.add(d.toString());
});
});
}
@override
void dispose() {
streamController.close();
super.dispose();
}
@override
Widget build(BuildContext context) {
print('build(): $itemCount');
return ListView.builder(
itemCount: itemCount,
itemBuilder: (BuildContext context, int index) {
final listItem = list[index];
return Card(
key: Key('card_$index'),
child: ListTile(
title: Text(listItem),
),
);
},
);
}
}
英文:
Use builder instead
import 'dart:async';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
brightness: Brightness.dark,
primaryColor: Colors.blueGrey,
useMaterial3: true,
),
home: Scaffold(body: W()),
debugShowCheckedModeBanner: false,
);
}
}
class W extends StatefulWidget {
const W({Key key}) : super(key: key);
@override
State<W> createState() => _WState();
}
class _WState extends State<W> {
StreamController<int> streamController;
List<String> list = []; // change the string to any data object
@override
void initState() {
super.initState();
streamController = StreamController<int>();
Stream<int> f() async* {
for (int i = 0; i < 100; ++i) {
await Future.delayed(Duration(seconds: 1));
yield i;
}
}
f().listen((d) {
setState(() {
list.add(d.toString());
});
});
}
@override
void dispose() {
streamController.close();
super.dispose();
}
@override
Widget build(BuildContext context) {
print('build(): $itemCount');
return ListView.builder(
itemCount: itemCount,
itemBuilder: (BuildContext context, int index) {
final listItem = list[index];
return Card(
key: Key('card_$index'),
child: ListTile(
title: Text(listItem),
),
);
},
);
}
}
答案3
得分: 0
不要缓存小部件。首选方法是存储状态 - 在这种情况下是接收到的字符串列表。在构建时,您可以动态创建小部件。
class _WState extends State<W> {
final strings = <String>[];
@override
void initState() {
super.initState();
Stream<int> f() async* {
for (int i = 0; i < 100; ++i) {
await Future.delayed(const Duration(seconds: 1));
yield i;
}
}
f().listen((d) {
setState(() {
strings.add(d.toString());
});
});
}
@override
Widget build(BuildContext context) {
return ListView(
children: strings
.map<Widget>((s) => ListTile(
title: Text(s),
))
.toList());
}
}
英文:
You shouldn't cache widgets. The preferred approach is to store the state - in this case the list of strings received. In build, you then create the widgets on the fly.
class _WState extends State<W> {
final strings = <String>[];
@override
void initState() {
super.initState();
Stream<int> f() async* {
for (int i = 0; i < 100; ++i) {
await Future.delayed(const Duration(seconds: 1));
yield i;
}
}
f().listen((d) {
setState(() {
strings.add(d.toString());
});
});
}
@override
Widget build(BuildContext context) {
return ListView(
children: strings
.map<Widget>((s) => ListTile(
title: Text(s),
))
.toList());
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论