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

Posted

技术标签:

【中文标题】Flutter - setState 不更新内部有状态小部件【英文标题】:Flutter - setState not updating inner Stateful Widget 【发布时间】:2018-07-28 10:15:08 【问题描述】:

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

编辑: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;
  

【问题讨论】:

请描述各种活动部件,并为预计会发生变化的事物提供示例值。我们需要输入和期望的输出。 我刚刚添加了有关该问题的更多信息。 您是否尝试过仅包装您的 _title = widget._title;在 setState 中? @Robert 是的,我必须添加子 Statefulwidget(带有 GestureRecognizer 的那个)工作正常,问题是尝试用 childWidget 的新实例刷新祖先 通常我所做的是创建一个未来并使用 Futurebuilder,这似乎每次都能为我完成这项工作。您也可以使用 Streams 做同样的事情。 【参考方案1】:

这应该可以解决您的问题。基本上,您总是希望在 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 'ItemDatatitle: $title';
    

【讨论】:

@JesúsMartín 我忘了说如果您有多个相同类型的列表项,那么您应该给每个列表项一个 Key。请参阅flutter.io/widgets-intro/#keys 了解更多信息。【参考方案2】:

这真的让我很头疼,而且 Google 搜索结果都没有用。最终奏效的方法如此简单。在您的孩子 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));


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

【讨论】:

谢谢,这对我有用。但我仍然很困惑,为什么没有它就不能工作..【参考方案3】:

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

WidgetsBinding.instance.addPostFrameCallback((_) => setState(...));

而不仅仅是setState(...)

【讨论】:

对于任何感兴趣的人:通过这样做,您在构建之后调用 setState()。【参考方案4】:

解释 Root 问题

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

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

作品

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),
        );
    

不工作

(当_title在parent中改变时它不会更新)

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),
        );
    

【讨论】:

请确保对层次结构中的 ALL 个子项执行此操作。很好的解释@eriel。【参考方案5】:

这解决了我的问题...如果您要为变量分配初始值,请在 initState() 中使用它

注意:当我尝试在构建函数中设置初始值时遇到了这个问题。

@override
  void initState() 
    count = widget.initialValue.length; // Initial value
    super.initState();
  

【讨论】:

【参考方案6】:

不要在未来中使用未来;使用不同的函数,像这样单独返回每个未来

 List<Requests> requestsData;
 List<DocumentSnapshot> requestsDocumentData;
 var docId;



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

    getRequestDocs();
  

  Future<FirebaseUser> getData() 
    var _auth = FirebaseAuth.instance;
    return _auth.currentUser();
  

  getRequestDocs() 
    getData().then((FirebaseUser user) 
      this.setState(() 
        docId = user.uid;
      );
    );

    FireDb()
        .getDocuments("vendorsrequests")
        .then((List<DocumentSnapshot> documentSnapshots) 
      this.setState(() 
        requestsDocumentData = documentSnapshots;
      );
    );

    for (DocumentSnapshot request in requestsDocumentData) 
      this.setState(() 
        requestsData.add(Requests(
            request.documentID,
            request.data['requests'],
            Icons.data_usage,
            request.data['requests'][0],
            "location",
            "payMessage",
            "budget",
            "tokensRequired",
            "date"));
      );
    
  

您可以为

创建单独的功能
  FireDb().getDocuments("vendorsrequests")
            .then((List<DocumentSnapshot> documentSnapshots) 
          this.setState(() 
            requestsDocumentData = documentSnapshots;
          );
        );

  for (DocumentSnapshot request in requestsDocumentData) 
          this.setState(() 
            requestsData.add(Requests(
                request.documentID,
                request.data['requests'],
                Icons.data_usage,
                request.data['requests'][0],
                "location",
                "payMessage",
                "budget",
                "tokensRequired",
                "date"));
          );
        

我发现使用

this

setState 是必须的

【讨论】:

【参考方案7】:

首先检查它是无状态的还是有状态的小部件,如果该类是无状态的则将其变为有状态的小部件并尝试在关闭后添加代码 setState(() _myState = newValue; );

【讨论】:

【参考方案8】:

找到最佳解决方案。 如果您使用的是无状态小部件,则不能使用设置状态,因此只需将无状态小部件转换为有状态小部件

【讨论】:

以上是关于Flutter - setState 不更新内部有状态小部件的主要内容,如果未能解决你的问题,请参考以下文章

Flutter ListView 不使用 setState() 刷新 UI,尽管 itemCount 和附加列表正确更新

编辑文本控制器更新小部件值而不调用 setState() - Flutter

在setState()之后更新Flutter页面

什么时候在 Flutter 中使用 setState?

如何在 FutureBuilder Flutter 中停止循环 SetState

Flutter的setState更新原理和流程