【问题标题】:Canceling a Firebase Listener inside a ChangeNotifier在 ChangeNotifier 中取消 Firebase 侦听器
【发布时间】:2021-12-20 07:17:55
【问题描述】:

当我尝试取消 Firestore 侦听器时 ProductsService().cancel()。我得到了错误:

[ERROR:flutter/lib/ui/ui_dart_state.cc(209)] 未处理的异常:LateInitializationError: 字段“productsSubscription”尚未初始化。

不迟到也不行:

StreamSubscription<QuerySnapshot>? productsSubscription;

我的代码:

import 'dart:async';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';

class ProductsService extends ChangeNotifier {
  late StreamSubscription<QuerySnapshot> productsSubscription;

  void cancel() => productsSubscription.cancel();

  void getMenuProducts() async {
    CollectionReference productsReference = FirebaseFirestore.instance.collection('products');
    productsSubscription = productsReference.snapshots().listen((snapshot) => print(snapshot.docs.length));
  }
}

我知道调用 .cancel() 可以在上下文中工作。 但我在带有 Riverpod 的 ChangeNotifier 中使用它。

带有 Riverpod 的流可以正常工作。真正的问题是,当用户注销并有新用户登录时,.listen 似乎仍然卡在旧的 firebase 用户上!并且不允许查询。 Firebase 规则使用 [cloud_firestore/permission-denied] 拒绝它们 - 当我重新启动应用程序时,查询工作。

我注意到的是,如果我可以在注销时获得 .listen canceled,则查询会在新用户登录时起作用。

因此,如果有人可以帮助我正确初始化它,或者让 Firebase 看到新用户,那将非常有帮助。 或任何其他建议。

【问题讨论】:

    标签: flutter google-cloud-firestore riverpod


    【解决方案1】:

    这似乎行得通,只是感觉很脆弱,但到目前为止还可以。

    服务类看起来像这样,里面有一个取消方法。

    class ProductsServiceNotifier extends ChangeNotifier {
      final FirebaseFirestore _db = FirebaseFirestore.instance;
      StreamSubscription<QuerySnapshot<Object?>>? productsSubscription;
      late List<ProductsModel> allProductsList = [];
      bool isLoading = true;
    
      void getShopProducts() async {
        var productsRef = _db.collection('products');
        productsSubscription = productsRef
            .where('entity', isEqualTo: sharedPrefs.activeShopEntityCode)
            .where('active', isEqualTo: true)
            .snapshots()
            .listen((snapshot) async {
          allProductsList = snapshot.docs.map((doc) => ProductsModel.fromMap(doc)).toList();
          isLoading = false;
          notifyListeners();
        });
      }
    
      Future cancelSub() async {
        if (productsSubscription != null) await productsSubscription?.cancel();
      }
    }
    

    然后像这样在注销时调用取消:

       TextButton(
          child: Text('Sign out'),
          onPressed: () async {
            await context.read(menuProductsNotifier).cancelSub();
            FirebaseAuth.instance.signOut();
            Navigator.of(context).pushNamedAndRemoveUntil('/', (Route<dynamic> route) => false);
          },              
        ),
    

    重要的是每次在我开始一个新的lister之前调用cancel()方法。否则,现有侦听器似乎卡在内存中的某个位置并导致 Firestore 规则权限被拒绝身份验证问题。 (用例是用户可以为启动新查询的不同实体调用产品,并需要监听它们。)

    • 我的 Riverpod 提供程序中的 autodispose 在我的场景中不起作用。

    • 我选择不使用 autodispose,因为它让我可以在侦听器上调用 cancel(),当然,如果它被释放,我将无法做到这一点。

    • 在 dispose 中调用 cancel 会产生错误 “查找已停用小部件的祖先是不安全的。” 所以这对我不起作用 - 我希望如此。

    我愿意接受任何更好的方法来做到这一点。感谢您的建议!

    【讨论】:

      【解决方案2】:

      如果您使用的是 Riverpod,那么您可以使用 ChangeNotifierProvider.autoDispose 并在处理时调用取消(或覆盖您的类 dispose 以调用取消)或侦听该提供程序中的用户名并在它触发更新时取消(登录 /退出状态)

      【讨论】:

      • 您好 EdwynZN,谢谢。不幸的是,我使用了 autodispose 并得到了相同的结果。
      • 我注意到的是,即使我确实将侦听器设置在上下文中,并将用户注销并重新登录,我仍然会得到 [cloud_firestore/permission-denied] - 除非我完全重新加载应用程序。这不应该是必要的。 FirebaseAuth 持有用户或该侦听器上的某些东西一定存在问题。重新加载 FB 用户也不起作用。我的 Firestore 规则不会有问题,重新加载应用程序时这些规则会起作用。我将为此发布一个单独的问题。
      • 您可以使用您的 Riverpod 代码以及您如何控制身份验证和更改通知程序来更新此问题,可能是 Firebase 的问题,或者可能是未来在不应该等待的地方
      • 我创建了一个新应用程序来重现最少的代码,并且监听器在注销和重新登录时停止。我导航和删除上下文或其他东西的方式一定有问题,超级困惑,会恢复的。
      猜你喜欢
      • 1970-01-01
      • 2021-09-30
      • 1970-01-01
      • 1970-01-01
      • 2017-02-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多