Flutter中使用Bloc如何不重复调用API同步更新多组件状态
跨组件Bloc联动状态更新实现方案
所有方案均无需重复调用初始数据加载接口,完全复用首次加载的缓存数据与acceptRequest接口返回值完成状态更新。
方案1:Bloc层依赖注入(架构规范性最优,推荐使用)
核心逻辑是在创建Bloc实例时,将页面共用的BlocA实例传入BlocB,直接在Bloc层完成双组件状态联动,无需把联动逻辑散落在UI层。
- 首先为
BlocA新增独立的条目更新事件,不复用初始数据拉取事件,避免触发多余接口请求:// 补全BlocA事件定义 abstract class BlocAEvent {} class GetData extends BlocAEvent { final String userid; GetData({required this.userid}); } // 新增:单独用于新增用户条目的事件 class AddUserToA extends BlocAEvent { final User newUser; AddUserToA({required this.newUser}); } - 在
BlocA中注册新增事件的处理逻辑,直接基于已缓存的状态做更新,不调用任何接口:class BlocA extends Bloc<BlocAEvent,BlocAState> { BlocA():super(InitialState()){ on<GetData>(_getBlocAData); on<AddUserToA>(_addUserToA); } // 原有初始数据拉取逻辑保持不变 void _getBlocAData(GetData event, Emitter<BlocAState>emit) async{ try { List<User> getDataResponse = await DataService().getWidgetAData(event.userid); emit(BlocALoadedState(BlocAData: getDataResponse)); } catch(e){ rethrow; } } // 新增:本地更新列表逻辑 void _addUserToA(AddUserToA event, Emitter<BlocAState>emit) { final currentState = state; if (currentState is BlocALoadedState) { // 基于现有缓存新建列表,不直接修改原状态属性 final updatedList = List<User>.from(currentState.BlocAData)..add(event.newUser); emit(BlocALoadedState(BlocAData: updatedList)); } } } - 修改
BlocB构造逻辑,持有BlocA的共用实例,在按钮点击事件中同时更新两个Bloc的状态:class BlocB extends Bloc<BlocBEvent,BlocBState> { final BlocA blocA; // 构造时传入页面共用的BlocA实例,不要在BlocB内部重新new BlocA BlocB({required this.blocA}):super(InitialState()){ on<GetData>(_getBlocBData); on<BlocBevent>(_onClickofaButtoninWidgetB); } // 原有B组件初始数据拉取逻辑保持不变 void _getBlocBData(GetData event, Emitter<BlocBState>emit) async{ try { List<User> getDataResponse = await DataService().getWidgetBData(event.userid); emit(BlocBLoadedState(BlocBdata: getDataResponse)); } catch(e){ rethrow; } } // 修改按钮点击事件处理逻辑 void _onClickofaButtoninWidgetB(BlocBevent event, Emitter<BlocBState>emit) async { User blocBEventResponse = await DataService().acceptRequest(event.senderId, event.receiverId); // 给BlocA发事件,新增条目 blocA.add(AddUserToA(newUser: blocBEventResponse)); // 更新BlocB自身状态,移除对应条目 final currentState = state; if (currentState is BlocBLoadedState) { final updatedBList = List<User>.from(currentState.BlocBdata) ..removeWhere((user) => user.id == blocBEventResponse.id); // 用用户唯一标识匹配删除项 emit(BlocBLoadedState(BlocBdata: updatedBList)); } // 触发B组件的操作成功状态 emit(BlocBAcceptedState(user: blocBEventResponse)); } } - 页面初始化Bloc时,保证两个Bloc共用同一个
BlocA实例即可:// 页面初始化逻辑 final blocA = BlocA(); final blocB = BlocB(blocA: blocA);
方案2:UI层状态监听联动(适合Bloc间需要完全解耦的场景)
如果不希望两个Bloc产生直接依赖,可以把联动逻辑放到UI层,通过BlocListener监听BlocB的状态变化,再触发BlocA的更新。
- 保留方案1中
BlocA新增的AddUserToA事件与对应处理逻辑,BlocB不需要持有BlocA引用,按钮点击事件仅处理自身状态更新:// BlocB中按钮点击逻辑简化为仅处理自身状态 void _onClickofaButtoninWidgetB(BlocBevent event, Emitter<BlocBState>emit) async { User blocBEventResponse = await DataService().acceptRequest(event.senderId, event.receiverId); final currentState = state; if (currentState is BlocBLoadedState) { final updatedBList = List<User>.from(currentState.BlocBdata) ..removeWhere((user) => user.id == blocBEventResponse.id); emit(BlocBLoadedState(BlocBdata: updatedBList)); } emit(BlocBAcceptedState(user: blocBEventResponse)); } - 在页面组件树中嵌套
BlocListener,监听BlocB的成功状态,触发BlocA更新:BlocListener<BlocB, BlocBState>( listener: (context, state) { if (state is BlocBAcceptedState) { context.read<BlocA>().add(AddUserToA(newUser: state.user)); } }, child: // 原有页面内容,包含组件A和组件B )
该方案的优势是两个Bloc完全独立无耦合,缺点是联动逻辑散落在UI层,复杂场景下维护成本更高。
方案3:全局事件总线(仅推荐超大型多模块项目使用)
如果项目已接入全局事件总线,可以在BlocB处理完按钮逻辑后,向总线发送用户请求已接受的事件,BlocA监听对应事件更新自身状态。该方案会导致事件流转不透明,问题排查难度高,小型项目不建议使用。
注意事项
- 所有状态更新必须做不可变处理,不要直接修改原状态里的List属性,必须新建List对象后再emit新状态,否则Bloc无法检测到状态变化,不会触发UI刷新。
- 列表增删操作必须用用户唯一标识(比如userId)做匹配,不要直接按列表索引操作,避免数据错位。
- 以上方案均不会触发初始数据接口重复调用,完全符合复用缓存数据的要求。
内容的提问来源于stack exchange,提问作者nidhi patnaik
相关产品推荐
相关产品推荐

