在 ChangeNotifier 中取消 Firebase 侦听器

Canceling a Firebase Listener inside a ChangeNotifier

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

[ERROR:flutter/lib/ui/ui_dart_state.cc(209)] Unhandled Exception: LateInitializationError: Field 'productsSubscription' has not been initialized.

不晚也不行:

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 看到新用户,那将非常有帮助。 或者任何其他建议。

如果您使用的是 Riverpod,那么您可以使用 ChangeNotifierProvider.autoDispose 并在处置时调用取消(或覆盖您的 class 处置以调用取消)或收听该提供商内的用户名并取消当它触发更新时(登录/注销状态)

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

服务 class 看起来像这样,其中有一个取消方法。

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);
      },              
    ),

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

  • 我的 Riverpod 提供程序中的自动处理在我的场景中不起作用。

  • 我选择不使用 autodispose,因为它让我可以在侦听器上调用 cancel(),当然,如果它被处理,我将无法执行。

  • 在 dispose 中调用取消会给出错误 “查找已停用的小部件的祖先是不安全的。” 所以这对我不起作用 - 我希望它能起作用。

我愿意接受任何更好的方法。感谢您的建议!