使用 Flutter Provider 将数据传递到另一个屏幕

Posted

技术标签:

【中文标题】使用 Flutter Provider 将数据传递到另一个屏幕【英文标题】:Passing data to another screen with Flutter Provider 【发布时间】:2021-07-14 10:52:38 【问题描述】:

我正在尝试使用 Provider 将数据传递到另一个屏幕,但似乎我总是传递相同的数据,除非我对列表进行排序然后传递不同的数据(这意味着我可能正在切换通过对列表进行排序来索引,这就是它现在传递不同数据的原因)。简而言之,我调用 API,填充列表,也为下一页设置提供程序,然后单击我列出上一个屏幕中的信息,但问题是我总是显示相同的项目,除非我对列表。代码如下:

调用 API 并显示列表:

 var posts = <RideData>[];
  var streamController = StreamController<List<RideData>>();

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

  _getRideStreamList() async 
    await Future.delayed(Duration(seconds: 3));
    var _vehicleStreamData = await APICalls.instance.getRides();

    var provider = Provider.of<RideStore>(context, listen: false);

    posts = await _vehicleStreamData
        .map<RideData>((e) => RideData.fromJson(e))
        .toList();

    streamController.add(posts);
    provider.setRideList(posts, notify: false);
  

  bool isSwitched = true;
  void toggleSwitch(bool value) 
    if (isSwitched == false) 
      posts.sort((k1, k2) => k1.rideId.compareTo(k2.rideId));
     else 
      posts.sort((k1, k2) => k2.rideId.compareTo(k1.rideId));
    
  

  @override
  Widget build(BuildContext context) 
    return Scaffold(
      backgroundColor: Colors.white,
      body: SafeArea(
        child: SingleChildScrollView(
          scrollDirection: Axis.vertical,
          child: Column(
            children: [
              TextButton(
                  child: Text('sort ascending'),
                  onPressed: () 
                    setState(() 
                      toggleSwitch(isSwitched = !isSwitched);
                    );
                  ),
              Container(
                height: 1000,
                child: StreamBuilder<List<RideData>>(
                    initialData: posts,
                    stream: streamController.stream,
                    builder: (context, snapshot) 
                      return ListView.builder(
                        shrinkWrap: true,
                        itemCount: snapshot.data.length,
                        itemBuilder: (context, index) 
                          return Column(
                            children: [
                              Row(
                                children: [
                                  Padding(
                                    padding: const EdgeInsets.only(left: 15.0),
                                    child: Text(
                                      'Ride #$snapshot.data[index].rideId',
                                    ),
                                  ),
                                  FlatButton(
                                    textColor: Colors.blue[700],
                                    minWidth: 0,
                                    child: Text('View'),
                                    onPressed: () 
// here is where I pass the data to the RideInfo screen
                                      Navigator.push(
                                          context,
                                          MaterialPageRoute(
                                              builder: (context) => RideInfo(
                                                    rideId: snapshot
                                                        .data[index].rideId,
                                                  )));
                                    ,
                                  ),
                                ],
                              ),
                              Column(
                                crossAxisAlignment: CrossAxisAlignment.stretch,
                                children: [
                                  Text(
                                    '$snapshot.data[index].pickupTime',
                                  ),
                                  Text(
                                    '$snapshot.data[index].jobArrived',
                                  ),
                                ],
                              ),
                            ],
                          );
                        ,
                      );
                    ),
              ),
            ],
          ),
        ),
      ),
    );
  

按下查看按钮并将数据传递到另一个屏幕 (RideInfo) 后:

class RideInfo extends StatefulWidget 
  static const String id = 'ride_info_screen';
  String rideId;
  RideInfo(@required this.rideId);

  @override
  _RideInfoState createState() => _RideInfoState();


class _RideInfoState extends State<RideInfo> 
  String rideID = '';

  @override
  void initState() 
    super.initState();
    setState(() 
      rideID = widget.rideId;
    );
  

  @override
  Widget build(BuildContext context) 
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Colors.white,
        title: Text(
          'Ride #$rideID',
          ),
        ),
      body: SafeArea(
        child: SingleChildScrollView(
          child: Consumer<RideStore>(
            builder: (context, rideStore, child) 
              return Column(
                children: [
                  ListView.builder(
                      shrinkWrap: true,
                      itemCount: 1,
                      itemBuilder: (context, index) 
                        RideData rides = rideStore.getRideByIndex(index);
                        return Column(
                          children: [
                            Expanded(
                              flex: 2,
                              child: Column(
                                children: [
                                  Text(
                                    "PICK UP",
                                  ),
// here I display the pickUpTime but it is always the same and I wanted to display the time based on the ID
                                  Text(
                                    '$rides.pickupTimeAM',
                                  ),
                                ],
                              ),
                            ),
                          ],
                        );
                      ),
                ],
              );
            ,
          ),
        ),
      ),
    );
  

当我按下查看单个项目的视图时,数据(在这种情况下为pickUpTime)并没有改变,但是就像我说的,当我用排序方法改变列表的顺序时,我得到了不同的数据。 这是提供者模型:

class RideStore extends ChangeNotifier 
  List<RideData> _rideList = [];

  List<RideData> get rideList => _rideList;

  setRideList(List<RideData> list, bool notify = true) 
    _rideList = list;
    if (notify) notifyListeners();
  

  RideData getRideByIndex(int index) => _rideList[index];

  int get rideListLength => _rideList.length;

如何根据我在骑行信息屏幕中按下并传递的列表中的 ID 显示正确的信息,这样它不会总是返回相同的数据?提前感谢您的帮助!

【问题讨论】:

【参考方案1】:

违规代码在RideInfo:

  ListView.builder(
      shrinkWrap: true,
      itemCount: 1,
      itemBuilder: (context, index) 
        RideData rides = rideStore.getRideByIndex(index);

index 始终为1,因此您始终显示第一个RideData。有多种选项可以修复它,例如将index,甚至将RideData 传递给RideInfo 构造函数:

class RideInfo extends StatefulWidget 
  static const String id = 'ride_info_screen';
  String rideId;
  final int index;
  RideInfo(@required this.rideId, @required this.index, Key key)
      : super(key: key) 

和:

        RideData rides = rideStore.getRideByIndex(widget.index);

我在代码上有一些额外的 cmets。首先,ListViewRideInfo 中毫无用处,因此请将其删除。

其次,无需构造streamController 并在父表单中使用StreamBuilder。您的列表可在RideStore 中找到。所以你的父表单可能有:

  Widget build(BuildContext context) 
    var data = Provider.of<RideStore>(context).rideList;
    ...
          Container(
            height: 1000,
            child:
                // StreamBuilder<List<RideData>>(
                //     initialData: posts,
                //     stream: streamController.stream,
                //     builder: (context, snapshot) 
                //       return
                ListView.builder(
                  shrinkWrap: true,
                  itemCount: data.length,

我希望这些 cmets 有所帮助。

编辑:

编辑您的代码以使用FutureBuilder 非常简单。首先,让_getRideStreamList返回它读取的数据:

  _getRideStreamList() async 
    ...
    return posts;
  

删除在initState 中对_getRideStreamList 的调用,并将ListView 包装在调用FutureBuilder_getRideStreamList 中:

          Container(
            height: 1000,
            child: FutureBuilder(
              future: _getRideStreamList(),
              builder: (ctx, snapshot) 
                if (!snapshot.hasData) 
                  return Center(child: CircularProgressIndicator());
                 else 
                  var data = snapshot.data;
                  return ListView.builder(
                    ...
                  );
                
              ,
            ),
          ),

这会在等待数据时显示CircularProgressIndicator

请注意,这是一个快速的技巧 - 您不希望每次重新构建小部件时都读取数据。所以_getRideStreamList 可以检查数据是否已经被读取并直接返回而不是重新读取。

【讨论】:

首先,谢谢你的详细回答,非常感谢!在这种情况下,我通过了index,我不确定如何成功地将完整的Ride Data 传递给Ride Info。其次,我使用了Stream Builder 没有Provider,所以我没有机会在发布之前对其进行重构,但再次感谢您的提醒,我真的很感激。干杯! 不用担心。您可以将RideData 传递给构造函数上的RideInfo,就像任何值一样:RideData ride; RideInfo(@required RideData this.ride); 根据您的建议,我删除了不必要的代码,现在的问题是我需要刷新我的小部件,因为 ListView.builder 在从 API 获取数据之前呈现小部件,我尝试使用FutureBuilder 并且我使用了“Provider”,因为我正在调用 API,但我遇到了同样的问题......

以上是关于使用 Flutter Provider 将数据传递到另一个屏幕的主要内容,如果未能解决你的问题,请参考以下文章

自定义状态管理Provider以及原理分析

如何在 Flutter 中使用 Provider 正确获取 API

Flutter - 了解 Provider、Bloc 的生命周期以及何时处理流 [重复]

如何在 Hot Reload 上使用 Provider 维护 Flutter Global BloC 状态?

如何在 Flutter 中正确重用 Provider

使用 Provider 将 Map 的状态保存到 Flutter 应用