发生更改时如何在Flutter中重建窗口小部件 [英] How to rebuild widget in Flutter when a change occurs

查看:54
本文介绍了发生更改时如何在Flutter中重建窗口小部件的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

编辑:我已经编辑了以下代码,以介绍用于获取数据的方法以及用于构建列车估算值的小部件(使用"API_URL替换所有API信息)的方法"" API_STOP_ID").我希望这能更好地帮助我们找出问题所在!我非常感谢任何人都可以提供的任何信息-我一直在为这个项目而努力!再次谢谢大家!

I've edited the code below to feature the method that fetches the data along with the widgets that build the train estimates (replacing any API information along the way with "API_URL" and "API_STOP_ID"). I hope this even better helps us figure out the problem! I really appreciate any information anyone can give -- I've been working very hard on this project! Thank you all again!

原始帖子:我有一个ListTiles的ListView,每个ListView都有一个尾随小部件,可在新的Text小部件中构建火车到达估计.这些尾随的小部件每五秒钟更新一次(由打印语句证明).当应用程序从火车的API获取数据时,它会显示_buildEstimatesNull()构建的无数据"文本窗口小部件.

Original post: I have a ListView of ListTiles that each have a trailing widget which builds train arrival estimates in a new Text widget. These trailing widgets are updated every five seconds (proven by print statements). As a filler for when the app is fetching data from the train's API, it displays a "no data" Text widget which is built by _buildEstimatesNull().

但是,问题是,即使应用程序已完成数据获取并且 _isLoading = false (已由print语句证明),仍然没有显示没有数据".尽管如此,即使解决了问题,火车的估算也会很快过时,因为尾随的小部件每隔五秒钟会更新一次,但这不会反映在实际的应用程序中,因为小部件是基于页面加载构建的.因此,我需要一种在尾随小部件获取新信息时对其进行重建的方法.

However, the problem is that "no data" is still being shown even when the app has finished fetching data and _isLoading = false (proven by print statements). Still, even if that was solved, the train estimates would become quickly outdated, as the trailing widgets are updating every five seconds on their own but this would not be reflected in the actual app as the widgets were built on page load. Thus, I need a way to rebuild those trailing widgets whenever they fetch new information.

是否有办法让Flutter每隔五秒(或每当_buildEstimatesS1更新一次/跟踪小部件的内部更新一次)自动重建ListTile的跟踪小部件?

Is there a way to have Flutter automatically rebuild the ListTile's trailing widget every five seconds as well (or whenever _buildEstimatesS1 is updated / the internals of the trailing widget is updated)?

class ShuttleApp extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return new ShuttleState();
  }
}

class ShuttleState extends State<ShuttleApp> {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      home: new HomeScreen(),
    );
  }
}

class HomeScreen extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return new HomeState();
  }
}

class HomeState extends State<HomeScreen> {

  var _isLoading = true;

  void initState() {
    super.initState();
    _fetchData();
    const fiveSec = const Duration(seconds: 5);
    new Timer.periodic(fiveSec, (Timer t) {
      _fetchData();
    });
  }

  var arrivalsList = new List<ArrivalEstimates>();

  _fetchData() async {
    arrivalsList.clear();
    stopsList.clear();
    final url = "API_URL";
    print("Fetching: " + url);
    final response = await http.get(url);
    final busesJson = json.decode(response.body);
    if (busesJson["service_id"] == null) {
      globals.serviceActive = false;
    } else {
      busesJson["ResultSet"]["Result"].forEach((busJson) {
        if (busJson["arrival_estimates"] != null) {
          busJson["arrival_estimates"].forEach((arrivalJson) {
            globals.serviceActive = true;
            final arrivalEstimate = new ArrivalEstimates(
                arrivalJson["route_id"], arrivalJson["arrival_at"], arrivalJson["stop_id"]
            );
            arrivalsList.add(arrivalEstimate);
          });
        }
      });
    }
    setState(() {
      _isLoading = false;
    });
  }

  Widget _buildEstimateNull() {
    return new Container(
      child: new Center(
        child: new Text("..."),
      ),
    );
  }

  Widget _buildEstimateS1() {
    if (globals.serviceActive == false) {
      print('serviceNotActive');
      _buildEstimateNull();
    } else {
      final String translocStopId = "API_STOP_ID";
      final estimateMatches = new List<String>();
      arrivalsList.forEach((arrival) {
        if (arrival.stopId == translocStopId) {
          estimateMatches.add(arrival.arrivalAt);
        }
      });
      estimateMatches.sort();
      if (estimateMatches.length == 0) {
        print("zero");
        return _buildEstimateNull();
      } else {
        return new Container(
          child: new Center(
            child: new Text(estimateMatches[0]),
          ),
        );
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
        backgroundColor: const Color(0xFF171717),
        appBar: new AppBar(),
        body: new DefaultTextStyle(
          style: new TextStyle(color: const Color(0xFFaaaaaa),),
          child: new ListView(
            children: <Widget>[
              new ListTile(
                title: new Text('S1: Forest Hills',
                    style: new TextStyle(fontWeight: FontWeight.w500, fontSize: 20.0)),
                subtitle: new Text('Orange Line'),
                contentPadding: new EdgeInsets.symmetric(vertical: 16.0, horizontal: 16.0),
                trailing: _isLoading ? _buildEstimateNull() : _buildEstimateS1(),
              ),
            ],
          ),
        )
    );
  }

class ArrivalEstimates {
  final String routeId;
  final String arrivalAt;
  final String stopId;
  ArrivalEstimates(this.routeId, this.arrivalAt, this.stopId);
}

非常感谢您提供的任何帮助!我真的非常感谢!:)

Thank you so much in advance for any help you can give! I really super appreciate it! :)

推荐答案

有几种方法可以解决此问题.但是,在不看更多代码的情况下很难告诉正在发生的事情-特别是如何获取数据以及如何处理数据.但是我想我还是可以给您一个充分的答案.

There are a few ways you could tackle this. It is slightly difficult however to tell what's going on without seeing a bit more of your code - specifically how you're getting the data and what you're doing with it. But I think I can give you a sufficient answer anyways.

执行此操作的简单方法是:

The simple way of doing this is to either:

  1. 具有一个StatefulWidget,该控件可跟踪列表中所有项目的构建估算.它应该从您的API请求数据,获取结果,然后调用 setState(()=> this.listData = data); .调用setState可以告诉窗口小部件需要重建.
  2. 对于列表中的每个项目都有一个StatefulWidget.他们每个人都每隔5秒执行一次API请求,获取结果,然后每个人都调用 setState(()=> this.itemData = data); .这意味着需要多次调用API等.
  1. Have a StatefulWidget which keeps track of the build estimates for all of the items in the list. It should request data from your API, get the results, and then call setState(() => this.listData = data);. The call to setState is what tells the widget that it needs to rebuild.
  2. Have a StatefulWidget for each item in the list. They would all each perform an API request every 5 seconds, get the results, and then each would call setState(() => this.itemData = data);. This means multiple calls to the API etc.

#1的优点是您可以批处理API调用,而#2的优点是您的构建的总体更改较少(尽管颤动的方式,这几乎是最小的)...所以我可能如果可能的话,请选择#1.

The advantage of #1 is that you can batch API calls, whereas the advantage to #2 is that your build would change less overall (although the way flutter works, this would be pretty minimal)... so I would probably go with #1 if possible.

但是,有一种更好的方法!

执行此操作的更好方法是拥有某种API管理器(或任何您想调用的API)来处理与API的通信.它可能会在您的窗口小部件树中更高的位置,并且可以使用所需的任何逻辑来启动/停止.根据小部件树的高度,您可以将其传递到每个子级中,或者更有可能将其保存在InheritedWidget中,然后将其用于从每个列表元素或整个列表中检索它.

The better way of doing this is to have some sort of API Manager (or whatever you want to call it) which handles the communication with your API. It probably would live higher up in your widget tree and would be started/stopped with whatever logic you want. Depending on how far up the widget tree is, you could either pass it into each child or more likely hold it in an InheritedWidget which could then be used to retrieve it from each list element or from the overall list.

API管理器将提供各种-要么带有一堆命名字段/methods或具有getStream(id)的某种结构,具体取决于您的API.

The API manager would provide various streams - either with a bunch of named fields/methods or with a getStream(id) sort of structure depending on your API.

然后,在各个列表元素中,您将使用 StreamBuilder 小部件可基于数据构建每个元素-通过使用StreamBuilder,您将获得一个ConnectionState对象,该对象可让您知道流是否已接收到任何数据,因此可以选择显示isLoading类型的小部件,而不是显示数据.

Then, within your various list elements, you would use StreamBuilder widgets to build each of the elements based on the data - by using a StreamBuilder you get a ConnectionState object that lets you know whether the stream has received any data yet so you can choose to show an isLoading type widget instead of the one that shows data.

使用这种更高级的方法,您将得到:

By using this more advanced method, you get:

  • 可维护性
    • 如果您的API发生更改,则只需更改API管理器
    • 由于API交互和UI交互是分开的,因此您可以编写更好的测试
    • 稍后,如果您使用推送通知进行更新,而不是每隔5秒对服务器进行一次ping操作,则可以将其合并到API管理器中,以便它可以简单地更新流而无需触摸UI

    根据OP的评论,他们已经或多或少地实施了第一条建议.但是,代码存在一些问题.我将在下面列出它们,并在代码中进行了一些更改.

    as per OP's comments, they have already implemented more or less the first suggestion. However, there are a few problems with the code. I'll list them below and I've posted the code with a couple of changes.

    1. 每次完成新构建时,都应替换 arrivalsList ,而不是简单地对其进行更改.这是因为dart会比较列表,如果找到相同的列表,则不一定会比较所有元素.另外,虽然在函数中间进行更改不一定会引起问题,但通常最好使用局部变量,然后在最后更改值.请注意,该成员实际上是在setState中设置的.
    2. 如果serviceActive == false,则返回 return _buildEstimateNull(); .
    1. The arrivalsList should be replaced each time a new build is done rather than simply being changed. This is because dart compares the lists and if it finds the same list, it doesn't necessarily compare all of the elements. Also, while changing it in the middle of a function isn't necessarily going to cause problems, it's generally better to use a local variable and then change the value at the end. Note that the member is actually set within setState.
    2. If serviceActive == false, the return was missed from return _buildEstimateNull();.

    代码如下:

    class HomeState extends State<HomeScreen> {
    
      var _isLoading = true;
    
      void initState() {
        super.initState();
        _fetchData();
        const fiveSec = const Duration(seconds: 5);
        new Timer.periodic(fiveSec, (Timer t) {
          _fetchData();
        });
      }
    
      var arrivalsList = new List<ArrivalEstimates>();
    
      _fetchData() async {
        var arrivalsList = new List<ArrivalEstimates>(); // *********** #1
        stopsList.clear();
        final url = "API_URL";
        print("Fetching: " + url);
        final response = await http.get(url);
        final busesJson = json.decode(response.body);
        if (busesJson["service_id"] == null) {
          print("no service id");
          globals.serviceActive = false;
        } else {
          busesJson["ResultSet"]["Result"].forEach((busJson) {
            if (busJson["arrival_estimates"] != null) {
              busJson["arrival_estimates"].forEach((arrivalJson) {
                globals.serviceActive = true;
                final arrivalEstimate = new ArrivalEstimates(
                    arrivalJson["route_id"], arrivalJson["arrival_at"], arrivalJson["stop_id"]
                );
                arrivalsList.add(arrivalEstimate);
              });
            }
          });
        }
        setState(() {
          _isLoading = false;
          this.arrivalsList = arrivalsList; // *********** #1
        });
      }
    
      Widget _buildEstimateNull() {
        return new Container(
          child: new Center(
            child: new Text("..."),
          ),
        );
      }
    
      Widget _buildEstimateS1() {
        if (globals.serviceActive == false) {
          print('serviceNotActive');
          return _buildEstimateNull();  // ************ #2
        } else {
          final String translocStopId = "API_STOP_ID";
          final estimateMatches = new List<String>();
          print("arrivalsList length: ${arrivalsList.length}");
          arrivalsList.forEach((arrival) {
            if (arrival.stopId == translocStopId) {
              print("Estimate match found: ${arrival.stopId}");
              estimateMatches.add(arrival.arrivalAt);
            }
          });
          estimateMatches.sort();
          if (estimateMatches.length == 0) {
            print("zero");
            return _buildEstimateNull();
          } else {
            return new Container(
              child: new Center(
                child: new Text(estimateMatches[0]),
              ),
            );
          }
        }
      }
    
      @override
      Widget build(BuildContext context) {
        return new Scaffold(
            backgroundColor: const Color(0xFF171717),
            appBar: new AppBar(),
            body: new DefaultTextStyle(
              style: new TextStyle(color: const Color(0xFFaaaaaa),),
              child: new ListView(
                children: <Widget>[
                  new ListTile(
                    title: new Text('S1: Forest Hills',
                        style: new TextStyle(fontWeight: FontWeight.w500, fontSize: 20.0)),
                    subtitle: new Text('Orange Line'),
                    contentPadding: new EdgeInsets.symmetric(vertical: 16.0, horizontal: 16.0),
                    trailing: _isLoading ? _buildEstimateNull() : _buildEstimateS1(),
                  ),
                ],
              ),
            )
        );
      }
    

    这篇关于发生更改时如何在Flutter中重建窗口小部件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆