首页 > 解决方案 > 发生更改时如何在 Flutter 中重建小部件

问题描述

编辑:我已经编辑了下面的代码,以使用获取数据的方法以及构建火车估计的小部件(沿途用"API_URL"和替换任何 API 信息"API_STOP_ID")。我希望这能更好地帮助我们找出问题所在!我非常感谢任何人可以提供的任何信息——我一直在为这个项目努力工作!再次感谢大家!

原始帖子: 我有一个 ListTiles 的 ListView,每个都有一个尾随小部件,该小部件在新的 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);
}

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

标签: dartflutter

解决方案


有几种方法可以解决这个问题。然而,在不看更多代码的情况下判断发生了什么有点困难 - 特别是您如何获取数据以及您正在使用它做什么。但我想无论如何我都可以给你一个足够的答案。

这样做的简单方法是:

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

#1 的优点是您可以批处理 API 调用,而 #2 的优点是您的构建整体变化较小(尽管颤振的工作方式,这将是非常小的)......所以我可能会选择 # 1 如果可能的话。

但是,有更好的方法来做到这一点!

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

API 管理器将提供各种——根据您的 API,要么带有一堆命名字段/方法,要么带有 getStream(id) 类型的结构。

然后,在您的各种列表元素中,您将使用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(),
              ),
            ],
          ),
        )
    );
  }

推荐阅读