Flutter - setState不更新内部状态窗口小部件

Jes*_*tín 15 android dart dart-async flutter

基本上我正在尝试创建一个应用程序,其内容将使用从网站获取信息的异步​​函数进行更新,但是当我尝试设置新状态时,它不会重新加载新内容.如果我调试应用程序,它会显示当前内容是新内容,但在"重建"整个窗口小部件后,它不会显示新信息.

编辑:loadData()方法,基本上读取带有http包的URL,该URL包含一个JSON文件,其内容每5分钟更新一次新消息.例如,带有体育实时记分牌的.json文件,其得分总是在变化,因此内容应始终随新结果而变化.

class mainWidget extends StatefulWidget
{    
  State<StatefulWidget> createState() => new mainWidgetState();
}

class mainWidgetState extends State<mainWidget>
{

  List<Widget> _data;
  Timer timer;

  Widget build(BuildContext context) {
     return new ListView(
              children: _data);
  }

  @override
  void initState() {
    super.initState();
    timer = new Timer.periodic(new Duration(seconds: 2), (Timer timer) async {
      String s = await loadData();
      this.setState(() {
        _data = <Widget> [new childWidget(s)];
      });
      });
  }
}

class childWidget extends StatefulWidget {
  childWidget(String s){
    _title = s;
  }

  Widget _title;

  createState() => new childState();
}

class childState extends State<gameCardS> {

  Widget _title;

  @override
  Widget build(BuildContext context) {
    return new GestureDetector(onTap: foo(),
       child: new Card(child: new Text(_title));

  }

  initState()
  {
    super.initState();
    _title = widget._title;
  }
}
Run Code Online (Sandbox Code Playgroud)

eri*_*mon 29

Root问题解释

  • initState()对于子 widget,仅在将 Widget 插入到树中时调用一次。因此,当您的子 Widget 变量在父 Widget 上发生更改时,它们将永远不会更新。从技术上讲,小部件的变量正在发生变化,您只是没有在状态类中捕获该变化。

  • build()是每次 Widget 中的某些内容发生变化时都会调用的方法。这就是@gregthegeek解决方案有效的原因。更新子窗口小部件的构建方法中的变量将确保它们从父窗口获取最新的变量。

作品

class ChildState extends State<ChildWidget> {
    late String _title;
    @override
    Widget build(BuildContext context) {
        _title = widget._title; // <==== IMPORTANT LINE
        return new GestureDetector(onTap: () => foo(),
            child: new Text(_title),
        );
    }
}
Run Code Online (Sandbox Code Playgroud)

不起作用

(当父级中的_title发生变化时,不会更新)

class ChildState extends State<ChildWidget> {
    late String _title;

    @override
    void initState() {
      super.initState();
      _title = widget._title; // <==== IMPORTANT LINE
    }

    @override
    Widget build(BuildContext context) {
        return new GestureDetector(onTap: () => foo(),
            child: new Text(_title),
        );
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 请确保对层次结构中的**所有**子项执行此操作。很好的解释@eriel。 (4认同)

gre*_*eek 20

这真的让我头疼,而且没有谷歌搜索结果有效。最终奏效的方法是如此简单。在您的孩子 build() 中,在您返回之前将值分配给局部变量。完成此操作后,一切都可以用于后续数据加载。我什至取出了 initState() 代码。

非常感谢@Simon。你的回答以某种方式激励我尝试这个。

在您的 childState 中:

@override
Widget build(BuildContext context) {
_title = widget._title; // <<< ADDING THIS HERE IS THE FIX
return new GestureDetector(onTap: foo(),
   child: new Card(child: new Text(_title));

}
Run Code Online (Sandbox Code Playgroud)

希望这适用于您的代码。对我来说,我对传入的整个 JSON 记录使用 Map,而不是单个字符串,但这应该仍然有效。


Sim*_*mon 16

这应该排除你的问题.基本上,您总是希望在build方法层次结构中创建小部件.

import 'dart:async';

import 'package:flutter/material.dart';

void main() => runApp(new MaterialApp(home: new Scaffold(body: new MainWidget())));

class MainWidget extends StatefulWidget {
    @override
    State createState() => new MainWidgetState();
}

class MainWidgetState extends State<MainWidget> {

    List<ItemData> _data = new List();
    Timer timer;

    Widget build(BuildContext context) {
        return new ListView(children: _data.map((item) => new ChildWidget(item)).toList());
    }

    @override
    void initState() {
        super.initState();
        timer = new Timer.periodic(new Duration(seconds: 2), (Timer timer) async {
            ItemData data = await loadData();
            this.setState(() {
                _data = <ItemData>[data];
            });
        });
    }


    @override
    void dispose() {
        super.dispose();
        timer.cancel();
    }

    static int testCount = 0;

    Future<ItemData> loadData() async {
        testCount++;
        return new ItemData("Testing #$testCount");
    }
}

class ChildWidget extends StatefulWidget {

    ItemData _data;

    ChildWidget(ItemData data) {
        _data = data;
    }

    @override
    State<ChildWidget> createState() => new ChildState();
}

class ChildState extends State<ChildWidget> {

    @override
    Widget build(BuildContext context) {
        return new GestureDetector(onTap: () => foo(),
            child: new Padding(
                padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 24.0),
                child: new Card(
                    child: new Container(
                        padding: const EdgeInsets.all(8.0),
                        child: new Text(widget._data.title),
                    ),
                ),
            )
        );
    }

    foo() {
        print("Card Tapped: " + widget._data.toString());
    }
}

class ItemData {
    final String title;

    ItemData(this.title);

    @override
    String toString() {
        return 'ItemData{title: $title}';
    }
}
Run Code Online (Sandbox Code Playgroud)

  • @JesúsMartín 我忘了提及如果您有多个相同类型的列表项,那么您应该给每个列表项一个键。有关更多信息,请参阅 https://flutter.io/widgets-intro/#keys。 (6认同)

can*_*a20 8

我不确定为什么在调用setState(...)异步函数时会发生这种情况,但一个简单的解决方案是使用:

WidgetsBinding.instance.addPostFrameCallback((_) => setState(...));
Run Code Online (Sandbox Code Playgroud)

而不仅仅是 setState(...)

  • 对于任何感兴趣的人:通过这样做,您将在构建后调用 setState() 。 (7认同)

mav*_*i.s 8

子 StatefulWidget 不重建的真正问题在于 KEY

嘿,我的讨论有点晚了,但我认为这很重要。不久前我也遇到了类似的问题,我什至来到这个帖子来获取一些想法。

就我而言,我只是widget.value直接进入childWidget 的方法,并且当我调用mainWidgetbuild时它没有更新。setState

然后我找到了这个视频: https://youtu.be/kn0EOS-ZiIc (何时使用按键 - Flutter Widgets 101 Ep. 4) - Google 开发人员在这里讨论了如何在 Flutter 中使用按键。

简短的答案是

在 a 中StatefulWidget,您传递的实际值存储在状态中,而不是像 a 那样存储在小部件本身中StatelessWidget

当您调用setStatemainWidget,Flutter 会沿着 widget 树走下去并检查每个 childWidget 的类型和键,以查看是否有任何更改。由于有状态 widgets 将它们的值存储在状态中,Flutter 认为子 widgets 没有改变(因为类型和键是相同的)并且不会重建它们,即使值改变了。

真正的解决方案是为 widget 提供一个包含正在更改的值的键,因此当 Flutter 沿着树走下去时,它会注意到键发生了变化,并重建有状态的 widget。

这里的其他解决方案可能也有效,但如果您想真正理解它,这个视频值得观看。