【问题标题】:Stream not updating when docId is changed dynamically动态更改 docId 时流不更新
【发布时间】:2021-08-12 01:10:17
【问题描述】:

当 mainDocId 的值动态更新时,streamDemo() 不会更新 doc('$mainDocId') 的值。我想在动态更改文档 ID 时更新小部件 HomeBody(),以便我可以根据用户选择的文档检索数据。

我使用 getx 作为 SM。我尝试使用 update() 方法更新值,但不起作用。

代码如下。

控制器:

class Controller extends GetxController {

  // onInit
  @override
  void onInit() {
    finalNewsModel.bindStream(streamDemo());
    super.onInit();
  }

 
  // list of document ids.
  List docIdList = [
    'USA',
    'New York',
    'Canada',
  ];

  //
  RxString mainDocId = 'USA'.obs;

  // method to change document id based on docId index.
  changeDocId(int index) {
    mainDocId(docIdList[index]);
  }

  //
  Rxn<List<NewsModel>> finalNewsModel = Rxn<List<NewsModel>>();

  //
  List<NewsModel> get newsModelList => finalNewsModel.value;

  //
  Stream<List<NewsModel>> streamDemo() {
    return FirebaseFirestore.instance
        .collection('news')
        .doc('$mainDocId')
        .snapshots()
        .map((ds) {
      var mapData = ds.data();
      List mapList = mapData['list'];
      List<NewsModel> modelList = [];
      mapList.forEach((element) {
        modelList.add(NewsModel.fromMap(element));
      });
      return modelList;
    });
  }
}

// 界面

class HomeBody extends StatefulWidget {
  @override
  _HomeBodyState createState() => _HomeBodyState();
}

class _HomeBodyState extends State<HomeBody> {
//
  final Controller _controller = Get.put<Controller>(Controller());

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Obx(() {
        if (_controller.newsModelList == null) {
          return Center(
              child: Text(
            'Please try later!',
          ));
        } else if (_controller.newsModelList.isEmpty) {
          return Text('Empty List');
        } else {
          return ListView.builder(
            itemCount: _controller.newsModelList.length,
            itemBuilder: (context, index) {
              final NewsModel _newsModel = _controller.newsModelList[index];
              return MyContainer(
                title: _newsModel.title,
                titleImage: _newsModel.titleImage,
                index: index,
              );
            },
          );
        }
      }),
    );
  }
}

底部导航栏:

bottomNavigationBar: Container(
        color: Colors.grey[300],
        height: 60.0,
        child: Padding(
          padding: EdgeInsets.all(8.0),
          child: GetBuilder<Controller>(
            builder: (context) => ListView.builder(
              shrinkWrap: true,
              scrollDirection: Axis.horizontal,
              itemCount: _controller.docIdList.length,
              itemBuilder: (context, index) {
                return FavCategoryTags(
                  tagName: _controller.docIdList[index],
                  onpress: () =>_controller.changeDocId(index),
                );
              },
            ),
          ),
        ),
      ),

型号:

class NewsModel {
  String title, titleImage, brief, source;
  List aList;

  NewsModel({this.title, this.titleImage, this.brief, this.aList, this.source});

  factory NewsModel.fromMap(dynamic fieldData) {
    return NewsModel(
      title: fieldData['title'],
      titleImage: fieldData['titleImage'],
      brief: fieldData['brief'],
      aList: fieldData['mediaDescList'],
      source: fieldData['source'],
    );
  }
}

【问题讨论】:

  • 根据我对您的代码的理解,streamDemo() 仅在onInit 上被调用,这是正确的吗?如果是这样,没有任何更新是有道理的,因为流永远不会用新的mainDocId 值重新实例化。您能否确认在您的执行路径中再次调用此函数是否解决了问题?
  • 问候 Lemos,是的,我只在 onInit() 方法中使用 streamDemo() 方法。我尝试在 BottomNavbar 的 onpressed 回调以及 changeDocid() 方法中调用 streamDemo()。它不更新。它打印更改的值但不更新。使用 Steambuilder 它会更新,但不会使用模型和 Getx。
  • 好吧,我认为您将需要为此使用流构建器,我建议您应用此community question 中提出的逻辑,因为它与您的非常相似(至少具有相同的目标)并解释如何为此目的使用流构建器。
  • 我确实知道如何使用 streambuilder 实现目标,但我很好奇如何将它与这个模型概念一起使用。由于根据读取,streambuilder 很昂贵。无论如何感谢您的回复。

标签: flutter dart google-cloud-firestore flutter-getx


【解决方案1】:

这里不需要StreamBuilder。一旦常规的Stream 绑定到RxType,你就可以开始了。当流数据发生变化时,它将更新。您只需要更新绑定调用,如下所示。

一个问题是你应该初始化为一个普通的RxList

不是这个

  Rxn<List<NewsModel>> finalNewsModel = Rxn<List<NewsModel>>();

像这样初始化它。

  RxList<NewsModel> finalNewsModel = <NewsModel>[].obs;

然后你可以失去这个吸气剂

  List<NewsModel> get newsModelList => finalNewsModel.value;

因为.value 不需要并且不能在正确初始化的RxList 上工作。您可以将 RxList 列表视为常规列表,而不是需要 .valueRxStringRxInt 等。

您的Obx 现在可以建立在finalNewsModel 之上,并且您可能会丢失空检查,因为finalNewsModel 被初始化为一个空列表,并且永远不会为空。

Obx(() {
            if (_controller.finalNewsModel.isEmpty) {
              return Text('Empty List');
            } else {
              return Expanded(
                child: ListView.builder(
                  itemCount: _controller.finalNewsModel.length,
                  itemBuilder: (context, index) {
                    final NewsModel _newsModel =
                        _controller.finalNewsModel[index];
                    return MyContainer(
                      title: _newsModel.title,
                      titleImage: _newsModel.titleImage,
                      index: index,
                    );
                  },
                ),
              );
            }
          }),

至于你想用你的BottomNavBar做什么:

在这里,您尝试通过更改 Document Id 来更改 Stream 本身的参数。当您绑定到 onInit 中的 Stream 时,它绑定到当时设置的 Document Id。因此,它只会更新 Document 中的更改,除非您将其绑定到新的 Stream。因此,在您的情况下,只需在 changeDocId() 方法中再次调用 finalNewsModel.bindStream(streamDemo()); 即可更新 Stream 参数。

  void changeDocId(int index) {
    mainDocId(docIdList[index]);
    finalNewsModel.bindStream(streamDemo()); // only needed because you're updating the Document Id
  }

您也不需要GetBuilder 在您的BottomNavBar 中,除非您需要在视觉上更改BottomNavBar 本身。您所做的只是根据硬编码List 的值更新RxString 的值。假设您确实需要 BottomNavBar 中的某些内容来重建,那将是您需要调用 update() 的唯一方案。

我没有完整的 Firebase 集合结构,但我使用简化的 NewsModel 对其进行了测试,并在 Firebase 控制台中更新 String 会立即更新 Obx 小部件。并且调用changeDocId() 会立即返回更新后的Document Id 的值。

编辑:另外,对于您正在做的事情,mainDocId 不需要是可观察的字符串。流总是比原始数据类型更昂贵,所以除非你能证明它是合理的,否则最好将其设为如下所示的常规字符串。它的工作原理完全相同。

 String mainDocId = 'USA';

  // method to change document id based on docId index.
  void changeDocId(int index) {
    mainDocId = docIdList[index];
    finalNewsModel.bindStream(streamDemo());
  }

【讨论】:

  • 它工作了罗伦。谢谢你的额外提示。另外,我应该在两个单独的屏幕中使用两个StreamBuilder 还是使用这个版本在 UI 上显示数据?两个StreamBuilder 和一个stream()onInIt() 中的读取量会减少还是模型版本的读取量会减少?
  • 没问题。有可能每次您重新绑定添加另一个文档读取的流时,但我不确定。您还可以为每个页面创建一个单独的RxList,并将它们绑定到每个docId,这与每个页面上的StreamBuilder 相同,而不必实际使用StreamBuilder。这样一来,当应用启动时,您就可以获取所有读取数据,然后您唯一需要更多读取数据的情况是当云中的某些内容发生变化并且 Firebase 将实时更新推送到用户设备时。
【解决方案2】:

试试这个:

changeDocId(int index) {
   mainDocId.value = docIdList[index];
 }

而不是

changeDocId(int index) {
   mainDocId.value(docIdList[index]);
 }

我相信通过调用方法 (rxVar()) 更新 Rx 不会触发小部件重建,但使用分配值更新会触发 (rxVar.value=)

希望这可以修复或至少让您更接近修复。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-09-09
    • 2020-05-08
    • 2020-01-28
    • 1970-01-01
    • 1970-01-01
    • 2022-10-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多