在 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 中调用取消会给出错误 “查找已停用的小部件的祖先是不安全的。” 所以这对我不起作用 - 我希望它能起作用。
我愿意接受任何更好的方法。感谢您的建议!
当我尝试取消 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 中调用取消会给出错误 “查找已停用的小部件的祖先是不安全的。” 所以这对我不起作用 - 我希望它能起作用。
我愿意接受任何更好的方法。感谢您的建议!