build() 被调用,但在热重载之前未更新显示。

huangapple go评论60阅读模式
英文:

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&lt;int&gt; f() async* {
  for (int i = 0; i &lt; 100; ++i) {
    await Future.delayed(Duration(seconds: 1));
    yield i;
  }
}

I have a state List&lt;Widget&gt; l = []; and want to append a Card to it every second by using the stream.

@override
void initState() {
    super.initState();
    f().listen((d) {
      setState(
          () =&gt; this.l.add(Card(child: ListTile(title: Text(d.toString())))));
    });
}

And here's the build() method:

@override
Widget build(BuildContext context) {
  print(&#39;build(): ${this.l.length}&#39;);
  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:

  1. flutter run -d macos to start the application.

  2. Even if build() is called every second, display remains fully black.

    build() 被调用,但在热重载之前未更新显示。

  3. If you press <kbd>r</kbd> key to perform hot reload, display is updated.

    build() 被调用,但在热重载之前未更新显示。

Full code here:

DartPad

import &#39;package:flutter/material.dart&#39;;

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&lt;W&gt; createState() =&gt; _WState();
}

class _WState extends State&lt;W&gt; {
  List&lt;Widget&gt; l = [];

  @override
  void initState() {
    super.initState();

    Stream&lt;int&gt; f() async* {
      for (int i = 0; i &lt; 100; ++i) {
        await Future.delayed(Duration(seconds: 1));
        yield i;
      }
    }

    f().listen((d) {
      setState(
          () =&gt; this.l.add(Card(child: ListTile(title: Text(d.toString())))));
    });
  }

  @override
  Widget build(BuildContext context) {
    print(&#39;build(): ${this.l.length}&#39;);
    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,它也不会更新。

更严格的解释

官方文档中 ListView() 构造函数 提到

> 与框架中的其他小部件一样,此小部件期望子项列表在传递到此处后不会被更改。有关更多详细信息,请参阅 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) =&gt; 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 &#39;dart:async&#39;;

import &#39;package:flutter/material.dart&#39;;

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&lt;W&gt; createState() =&gt; _WState();
}

class _WState extends State&lt;W&gt; {
  StreamController&lt;int&gt; streamController;
  List&lt;String&gt; list = []; // change the string to any data object

  @override
  void initState() {
    super.initState();

    streamController = StreamController&lt;int&gt;();

    Stream&lt;int&gt; f() async* {
      for (int i = 0; i &lt; 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(&#39;build(): $itemCount&#39;);
    return ListView.builder(
      itemCount: itemCount,
      itemBuilder: (BuildContext context, int index) {
        final listItem = list[index];
        return Card(
          key: Key(&#39;card_$index&#39;),
          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&lt;W&gt; {
final strings = &lt;String&gt;[];
@override
void initState() {
super.initState();
Stream&lt;int&gt; f() async* {
for (int i = 0; i &lt; 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&lt;Widget&gt;((s) =&gt; ListTile(
title: Text(s),
))
.toList());
}
}

huangapple
  • 本文由 发表于 2023年5月21日 21:31:45
  • 转载请务必保留本文链接:https://go.coder-hub.com/76300158.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定