Flutter中基于Bloc与Cloud Firestore实现数据自动更新的方法
解决方案:Bloc 结合 Firestore 实现数据更新与双向实时同步
一、新增用户数据的 Bloc 处理逻辑
扩展现有 Bloc 的事件、状态体系,新增创建用户的处理逻辑,确保数据更新后界面自动同步。
1. 定义扩展的事件与状态
// 事件类 abstract class UserEvent {} class FetchUsers extends UserEvent {} class CreateUser extends UserEvent { final User user; CreateUser(this.user); } class UpdateUserText extends UserEvent { final String userId; final String newText; UpdateUserText(this.userId, this.newText); } // 状态类 abstract class UserState {} class UserInitial extends UserState {} class UserLoading extends UserState {} class UserLoaded extends UserState { final List<User> users; UserLoaded(this.users); } class UserOperationSuccess extends UserState {} class UserError extends UserState { final String message; UserError(this.message); }
2. User 数据模型
class User { final String? id; final String name; final String displayText; User({this.id, required this.name, required this.displayText}); // 转换为 Firestore 存储的 Map 格式 Map<String, dynamic> toMap() { return { 'name': name, 'displayText': displayText, }; } // 从 Firestore 文档快照转换为模型 factory User.fromFirestore(DocumentSnapshot doc) { final data = doc.data() as Map<String, dynamic>; return User( id: doc.id, name: data['name'], displayText: data['displayText'], ); } }
3. 扩展 Bloc 核心逻辑
在主 Bloc 中添加创建用户、文本更新的处理,并通过 Firestore 快照监听实现实时数据同步:
class UserBloc extends Bloc<UserEvent, UserState> { final FirebaseFirestore _firestore = FirebaseFirestore.instance; StreamSubscription? _usersSubscription; UserBloc() : super(UserInitial()) { // 绑定事件处理器 on<FetchUsers>(_onFetchUsers); on<CreateUser>(_onCreateUser); on<UpdateUserText>(_onUpdateUserText); // 监听 Firestore 集合实时变化,自动同步数据到界面 _usersSubscription = _firestore.collection('users').snapshots().listen((snapshot) { final users = snapshot.docs.map((doc) => User.fromFirestore(doc)).toList(); emit(UserLoaded(users)); }); } // 初始拉取用户数据 void _onFetchUsers(FetchUsers event, Emitter<UserState> emit) async { emit(UserLoading()); try { final snapshot = await _firestore.collection('users').get(); final users = snapshot.docs.map((doc) => User.fromFirestore(doc)).toList(); emit(UserLoaded(users)); } catch (e) { emit(UserError(e.toString())); } } // 创建新用户 void _onCreateUser(CreateUser event, Emitter<UserState> emit) async { try { await _firestore.collection('users').add(event.user.toMap()); emit(UserOperationSuccess()); // 无需手动拉取新数据,Firestore 快照监听会自动推送更新 } catch (e) { emit(UserError(e.toString())); } } // 更新用户文本 void _onUpdateUserText(UpdateUserText event, Emitter<UserState> emit) async { try { await _firestore.collection('users').doc(event.userId).update({ 'displayText': event.newText, }); emit(UserOperationSuccess()); } catch (e) { emit(UserError(e.toString())); } } // 取消订阅避免内存泄漏 @override Future<void> close() { _usersSubscription?.cancel(); return super.close(); } }
二、双向实时同步实现
核心逻辑是Firestore 快照监听 + 本地操作触发 Firestore 更新,形成闭环:
- 从 Firestore 到界面:Bloc 通过
snapshots()监听集合变化,一旦 Firestore 中文档新增/修改,自动触发UserLoaded状态,界面通过BlocBuilder刷新展示最新数据。 - 从界面到 Firestore:用户在界面修改文本后,提交时触发
UpdateUserText事件,Bloc 调用 Firestore 的update方法修改对应文档;随后快照监听到变化,再次推送更新后的状态到界面,完成双向同步。
三、主界面实现示例
class HomeScreen extends StatelessWidget { final TextEditingController _nameController = TextEditingController(); final TextEditingController _textController = TextEditingController(); HomeScreen({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('用户列表')), body: BlocBuilder<UserBloc, UserState>( builder: (context, state) { if (state is UserLoading) { return const Center(child: CircularProgressIndicator()); } else if (state is UserLoaded) { return ListView.builder( itemCount: state.users.length, itemBuilder: (context, index) { final user = state.users[index]; return ListTile( title: Text(user.name), subtitle: TextField( controller: TextEditingController(text: user.displayText), onSubmitted: (newText) { // 提交修改触发更新事件 context.read<UserBloc>().add(UpdateUserText(user.id!, newText)); }, decoration: const InputDecoration(hintText: '修改展示文本'), ), ); }, ); } else if (state is UserError) { return Center(child: Text(state.message)); } else { return const Center(child: Text('点击下方按钮加载用户')); } }, ), floatingActionButton: Column( mainAxisAlignment: MainAxisAlignment.end, children: [ FloatingActionButton( onPressed: () { showDialog( context: context, builder: (context) => AlertDialog( title: const Text('创建新用户'), content: Column( mainAxisSize: MainAxisSize.min, children: [ TextField( controller: _nameController, decoration: const InputDecoration(hintText: '输入用户名'), ), TextField( controller: _textController, decoration: const InputDecoration(hintText: '输入展示文本'), ), ], ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('取消'), ), TextButton( onPressed: () { final user = User( name: _nameController.text, displayText: _textController.text, ); // 触发创建用户事件 context.read<UserBloc>().add(CreateUser(user)); _nameController.clear(); _textController.clear(); Navigator.pop(context); }, child: const Text('创建'), ), ], ), ); }, child: const Icon(Icons.add), ), const SizedBox(height: 10), FloatingActionButton( onPressed: () => context.read<UserBloc>().add(FetchUsers()), child: const Icon(Icons.refresh), ), ], ), ); } }
关键注意事项
- 务必在 Bloc 的
close方法中取消StreamSubscription,避免内存泄漏。 - 确保 Firestore 规则允许对应的读写操作,否则会出现权限错误。
- 界面中使用
BlocBuilder来监听状态变化,确保数据更新时界面自动重建。
内容的提问来源于stack exchange,提问作者Gorge Reyes
相关产品推荐
相关产品推荐

