使用Bloc替代Stream Builder实时更新Firestore时数据异常求助
个人主页Bloc数据混淆问题排查与解决
我基于Bloc模式(数据、领域、表现层)开发社交APP时,个人主页模块出现异常:本机关注他人后自身关注数增加,对方设备打开该被关注者的个人主页时,显示的却是我的个人主页数据。用Stream Builder时无此问题,相关代码如下:
Profile Bloc代码
class ProfileBLoc extends Bloc<ProfileEvent, ProfileState> { final GetProfileDataUseCase getProfileDataUseCase; ProfileBLoc({ required this.getProfileDataUseCase, }) : super(ProfileInitialState()) { on<ProfileLoadEvent>((event, emit) async { await emit.forEach( getProfileDataUseCase.call(event.uid), onData: (data) { print(data); final user = UserModel.fromSnapshot(data); return ProfileFoundState(user: user); }, ); }); } }
Remote Data Source代码
@override Stream<DocumentSnapshot<Map<String, dynamic>>> getProfileData(String uid) { return firestore.collection("users").doc(uid).snapshots(); }
Repository代码
@override Stream<DocumentSnapshot<Map<String, dynamic>>> getProfileData(String uid) => profileRemoteDataSource.getProfileData(uid);
Use Case代码
class GetProfileDataUseCase { final ProfileRepository repository; GetProfileDataUseCase({required this.repository}); Stream<DocumentSnapshot<Map<String, dynamic>>> call(String uid) => repository.getProfileData(uid); }
问题原因
核心问题是Bloc的事件处理未管理流订阅的生命周期:当新的ProfileLoadEvent触发时,之前的流订阅没有被取消,导致多个不同uid的流同时向Bloc发送状态,后续旧流的状态会覆盖新流的正确数据,最终出现数据串流的情况。而Stream Builder每次重建都会重新监听对应uid的流,天然避免了多流并发的问题。
解决方法
方法1:手动管理流订阅
在Bloc中维护订阅对象,每次触发新事件时取消旧订阅,同时在Bloc销毁时清理订阅:
class ProfileBloc extends Bloc<ProfileEvent, ProfileState> { final GetProfileDataUseCase getProfileDataUseCase; StreamSubscription? _profileSubscription; ProfileBloc({ required this.getProfileDataUseCase, }) : super(ProfileInitialState()) { on<ProfileLoadEvent>((event, emit) async { // 取消之前的旧订阅,避免多流并发 await _profileSubscription?.cancel(); // 订阅当前uid的流 _profileSubscription = getProfileDataUseCase.call(event.uid).listen((data) { final user = UserModel.fromSnapshot(data); emit(ProfileFoundState(user: user)); }); }); } // Bloc销毁时清理订阅,防止内存泄漏 @override Future<void> close() { _profileSubscription?.cancel(); return super.close(); } }
方法2:使用Restartable Transformer
借助Bloc提供的restartable()转换器,自动取消旧事件的流处理,只保留最新事件对应的流:
class ProfileBloc extends Bloc<ProfileEvent, ProfileState> { final GetProfileDataUseCase getProfileDataUseCase; ProfileBloc({ required this.getProfileDataUseCase, }) : super(ProfileInitialState()) { on<ProfileLoadEvent>( (event, emit) async* { await for (final data in getProfileDataUseCase.call(event.uid)) { final user = UserModel.fromSnapshot(data); yield ProfileFoundState(user: user); } }, transformer: restartable(), // 自动终止旧事件的流处理 ); } }
两种方法都能解决多流并发导致的数据混淆问题,推荐使用方法2,代码更简洁且符合Bloc的最佳实践。
内容的提问来源于stack exchange,提问作者Vishal Kumar
相关产品推荐
相关产品推荐

