Flutter Bloc实现列表多选及相关问题优化咨询
问题分析与解决方案
一、提交导航与状态显示异常修复
1. 导航异常修复
原代码中BlocConsumer的listener判断的是AuthSucess,但Bloc成功时发出的是BlogSucess,类型不匹配导致导航逻辑未触发。同时提交成功后需要重置选中状态。
2. 状态显示异常修复
- 移除Bloc类中的
selectedTopic成员变量,将选中状态完全托管在BlogInitial中,遵循Bloc单一数据源原则,避免状态不一致。 - 加载状态下,列表项不再显示占位文本,而是保持Chip样式并禁用交互,保证UI连贯性。
- 优化AppBar按钮的状态判断,覆盖
BlogLoading/BlogInitial/BlogSucess/BlogFailure所有状态,避免停留在异常UI。
二、提交/返回时清空选中主题实现
- 新增
ClearTopicsEvent事件及处理逻辑,提交成功后自动触发清空。 - 使用
WillPopScope监听页面返回事件,返回前触发清空操作。
修改后代码实现
1. blog_bloc.dart
class BlogBloc extends Bloc<BlogEvent, BlogState> { final UploadBlog uploadBlog; BlogBloc( this.uploadBlog, ) : super(const BlogInitial(topics: [])) { on<BlogUploadEvent>(_uploadBlog); on<SelectTopicsEvent>(_selectTopic); on<ClearTopicsEvent>(_clearTopics); // 新增清空事件处理 } void _selectTopic(SelectTopicsEvent event, Emitter<BlogState> emit) { // 从当前状态获取选中项,不再依赖类成员变量 final currentState = state as BlogInitial; final updatedTopics = List<String>.from(currentState.topics); if (updatedTopics.contains(event.topic)) { updatedTopics.remove(event.topic); } else { updatedTopics.add(event.topic); } emit(BlogInitial(topics: updatedTopics)); } void _uploadBlog(BlogUploadEvent event, Emitter<BlogState> emit) async { emit(BlogLoading()); final res = await uploadBlog.call( posterId: event.posterId, title: event.title, content: event.content, image: event.image, topics: event.topics, ); res.fold( (l) => emit(BlogFailure(l.message.toString())), (r) { emit(const BlogSucess()); // 提交成功后自动清空选中项 add(ClearTopicsEvent()); }, ); } // 新增清空选中项逻辑 void _clearTopics(ClearTopicsEvent event, Emitter<BlogState> emit) { emit(const BlogInitial(topics: [])); } }
2. blog_event.dart
part of 'blog_bloc.dart'; @immutable sealed class BlogEvent {} final class SelectTopicsEvent extends BlogEvent { final String topic; SelectTopicsEvent(this.topic); } final class BlogUploadEvent extends BlogEvent { final String posterId; final String title; final String content; final dynamic image; final List<String> topics; BlogUploadEvent({ required this.posterId, required this.title, required this.content, required this.image, required this.topics, }); } // 新增清空选中项事件 final class ClearTopicsEvent extends BlogEvent {}
3. UI代码
Widget build(BuildContext context) { return WillPopScope( // 监听页面返回事件 onWillPop: () async { context.read<BlogBloc>().add(ClearTopicsEvent()); return true; }, child: Scaffold( appBar: AppBar( actions: [ BlocBuilder<BlogBloc, BlogState>( builder: (context, state) { if (state is BlogLoading) { return const CircularProgressIndicator.adaptive(); } else if (state is BlogInitial) { return IconButton( onPressed: () { if (formKey.currentState?.validate() ?? false) { context.read<BlogBloc>().add( BlogUploadEvent( posterId: posterId, title: titleController.text.trim(), content: contentController.text.trim(), image: image!, topics: state.topics, ), ); } }, icon: const Icon(Icons.done_rounded), ); } else { // 成功/失败状态下显示可点击的完成按钮,支持手动返回 return IconButton( onPressed: () { context.read<BlogBloc>().add(const ClearTopicsEvent()); Navigator.of(context).pop(); }, icon: const Icon(Icons.done_rounded), ); } }, ), ], ), body: BlocConsumer<BlogBloc, BlogState>( listener: (context, state) { if (state is BlogFailure) { log(state.error); showSnackbar(context: context, message: state.error); } if (state is BlogSucess) { Navigator.of(context).pushNamedAndRemoveUntil( RouteName.blogPage, (route) => false, ); } }, builder: (context, state) { // 统一获取选中项,兼容所有状态 final selectedTopics = state is BlogInitial ? state.topics : const []; final isLoading = state is BlogLoading; return SingleChildScrollView( child: Padding( padding: const EdgeInsets.all(16.0), child: Form( key: formKey, child: Column( children: [ // 省略原有表单组件... SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( children: [ 'Technology', 'Entertainment' ] .map( (value) => GestureDetector( onTap: isLoading ? null : () { context.read<BlogBloc>().add(SelectTopicsEvent(value)); }, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 8), child: Chip( label: Text(value), color: selectedTopics.contains(value) ? const WidgetStatePropertyAll(AppPallete.gradient1) : null, side: selectedTopics.contains(value) ? null : const BorderSide(color: AppPallete.borderColor), enabled: !isLoading, // 加载时禁用Chip交互 ), ), ), ) .toList(), ), ), ], ), ), ), ); }, ), ), ); }
4. blog_state.dart(无改动)
part of 'blog_bloc.dart'; @immutable sealed class BlogState {} final class BlogInitial extends BlogState { final List<String> topics; const BlogInitial({required this.topics}); } final class BlogLoading extends BlogState {} final class BlogSucess extends BlogState {} final class BlogFailure extends BlogState { final String error; BlogFailure(this.error); }
内容的提问来源于stack exchange,提问作者Akhil George
相关产品推荐
相关产品推荐

