Flutter开发中ListView.builder内Bloc事件仅触发一次问题咨询
问题根源
- 所有列表项共用同一个
UsersWatcherBloc实例,Bloc单次只能维护一份状态,新事件触发的请求结果会直接覆盖之前的状态,所以看起来仅一次事件生效 - 你在
itemBuilder中直接触发Bloc事件属于错误写法,build方法会因页面重绘多次触发,容易造成重复请求 - 现有
UsersWatcherState仅支持存储单份用户查询结果,无法同时保存多个userId对应的用户数据
解决方案
方案1:单Bloc缓存多用户数据(适用列表项多、重复userId多的场景)
改造Bloc的状态结构,用Map缓存每个userId对应的加载状态和数据,避免状态互相覆盖:
- 重新定义State结构
part of 'users_watcher_bloc.dart'; @freezed abstract class UsersWatcherState with _$UsersWatcherState { const factory UsersWatcherState({ // key为userId,value为对应用户的加载状态 required Map<String, UserLoadState> userStateMap, }) = _UsersWatcherState; factory UsersWatcherState.initial() => const UsersWatcherState(userStateMap: {}); } // 定义单用户的加载状态 @freezed abstract class UserLoadState with _$UserLoadState { const factory UserLoadState.loadInProgress() = _LoadInProgress; const factory UserLoadState.loadFailure(Failure f) = _LoadFailure; const factory UserLoadState.empty() = _Empty; const factory UserLoadState.loadSuccess(User user) = _LoadSuccess; }
- 改造Bloc逻辑
@override Stream<UsersWatcherState> mapEventToState( UsersWatcherEvent event, ) async* { yield* event.map( watchAllUsers: (e) async* { final userId = e.uId; // 先标记当前用户为加载中,不影响其他用户的状态 yield state.copyWith( userStateMap: Map.from(state.userStateMap) ..[userId] = const UserLoadState.loadInProgress(), ); // 发起请求 yield* _iElearningRepository.watchAllUsers(userId).map( (failureOrUsers) => failureOrUsers.fold( (f) => state.copyWith( userStateMap: Map.from(state.userStateMap) ..[userId] = UserLoadState.loadFailure(f), ), (users) { final userState = users.isEmpty ? const UserLoadState.empty() : UserLoadState.loadSuccess(users.first); return state.copyWith( userStateMap: Map.from(state.userStateMap) ..[userId] = userState, ); }, ), ); }, ); }
- 改造列表项逻辑
itemBuilder: (context, index) { final question = state.questions.get(index); final userId = question.userId.getorCrash(); final bloc = context.read<UsersWatcherBloc>(); // 仅无缓存时触发请求,用addPostFrameCallback避免在build过程中修改状态 if (!bloc.state.userStateMap.containsKey(userId)) { WidgetsBinding.instance.addPostFrameCallback((_) { bloc.add(UsersWatcherEvent.watchAllUsers(userId)); }); } // 只监听当前userId对应的状态变化 return BlocBuilder<UsersWatcherBloc, UsersWatcherState>( builder: (context, state) { final currentUserState = state.userStateMap[userId]; // 根据currentUserState渲染对应UI即可 return Container( color: Colors.white, // 原有布局代码 ); }, ); }
方案2:每个列表项独立Bloc实例(适用列表项少、实现要求简单的场景)
- 把
UsersWatcherBloc在injectable中注册为工厂模式,保证每次调用获取的都是新实例 - 把列表项抽为独立组件,每个组件单独创建Bloc实例:
// 独立列表项组件 class QuestionItem extends StatefulWidget { final Question question; const QuestionItem({super.key, required this.question}); @override State<QuestionItem> createState() => _QuestionItemState(); } class _QuestionItemState extends State<QuestionItem> { @override void initState() { super.initState(); // 仅初始化时触发一次请求 context.read<UsersWatcherBloc>().add( UsersWatcherEvent.watchAllUsers( widget.question.userId.getorCrash(), ), ); } @override Widget build(BuildContext context) { return BlocBuilder<UsersWatcherBloc, UsersWatcherState>( builder: (context, state) { // 直接用当前Bloc的状态渲染即可,和其他列表项互不干扰 return Container( color: Colors.white, // 原有布局代码 ); }, ); } } // ListView的itemBuilder改造 itemBuilder: (context, index) { final question = state.questions.get(index); return BlocProvider( // 每个列表项创建独立的Bloc实例 create: (context) => getIt<UsersWatcherBloc>(), child: QuestionItem(question: question), ); }
内容的提问来源于stack exchange,提问作者Amit Singh
相关产品推荐
相关产品推荐

