Flutter - setState 不更新内部 Stateful Widget

Flutter - setState not updating inner Stateful Widget

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

编辑:loadData()方法,主要是读取一个URL with http包,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;
  }
}

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

不要在未来中使用未来;使用不同的功能 return 每个未来都像这样

 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 是必须的

这真的让我很头疼,没有 Google 结果有效。最终奏效的方法非常简单。在您的 child build() 中,将值分配给 return 之前的局部变量。一旦我这样做了,一切都适用于后续的数据加载。我什至拿出了 initState() 代码。

非常感谢@Simon。您的回答以某种方式启发了我尝试这个。

在你的child州:

@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,而不是单个 String,但这应该仍然有效。

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

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

而不只是 setState(...)

这解决了我的问题...如果您有一个要分配给变量的初始值,请在 initState()

中使用它

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

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

Root 问题解释

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

  • build() is the method that gets called every time something in the Widget changes. This is the reason 解决方案有效。更新子部件构建方法中的变量将确保它们从父部件获得最新的。

有效

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

不起作用

(parent_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),
        );
    }
}

首先检查它是无状态还是有状态的widget,如果class是无状态的那么把它变成有状态的widget,关闭后尝试添加代码 setState(() { _myState = newValue; });

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

在我的例子中,只是 将状态定义为 class 属性 而不是构建方法中的局部变量

这样做 -

  List<Task> tasks = [
    Task('Buy milk'),
    Task('Buy eggs'),
    Task('Buy bread'),
  ];
  @override
  Widget build(BuildContext context) {
  
    return ListView.builder(
      itemBuilder: (context, index) => TaskTile(
...

而不是这个 -

 @override
  Widget build(BuildContext context) {
  List<Task> tasks = [
    Task('Buy milk'),
    Task('Buy eggs'),
    Task('Buy bread'),
  ];
  
    return ListView.builder(
      itemBuilder: (context, index) => TaskTile(
...