发生更改时如何在Flutter中重建窗口小部件

ctr*_*inh 4 dart flutter

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

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

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

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

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);
}
Run Code Online (Sandbox Code Playgroud)

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

rmt*_*zie 5

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

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

  1. 有一个StatefulWidget,用于跟踪列表中所有项目的构建估算。它应该从您的API请求数据,获取结果,然后调用setState(() => this.listData = data);。调用setState可以告诉窗口小部件需要重建。
  2. 列表中的每个项目都有一个StatefulWidget。他们每个人都会每5秒执行一次API请求,获取结果,然后每个人都将调用setState(() => this.itemData = data);。这意味着多次调用API等。

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

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

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

API管理器将提供各种 -要么具有一堆命名字段/方法,要么具有getStream(id)结构类型,具体取决于您的API。

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

通过使用这种更高级的方法,您可以获得:

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

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

  1. arrivalsList应在每次新的构建完成,而不是简单地改变及时更换。这是因为dart会比较列表,并且如果找到相同的列表,则不一定会比较所有元素。同样,虽然在函数中间进行更改不一定会引起问题,但通常最好使用局部变量,然后在最后更改值。请注意,该成员实际上是在setState中设置的。
  2. 如果serviceActive == false,则返回缺少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(),
              ),
            ],
          ),
        )
    );
  }
Run Code Online (Sandbox Code Playgroud)