Flutter BlocBuilder如何避免重建ListView并正确处理Failed状态?
问题
需求说明:
- 当状态为
PostSuccess时,显示成功snackbar并通过Navigator.pop()返回上一页; - 当状态为
Failed时,显示错误snackbar,且不重建ListView.builder
当前问题:PostSuccess状态工作正常,但触发EduWorkFailed状态时,snackbar能正常显示,但界面会显示Text('Error')。
相关代码片段:
BlocListener 代码
if (state is EduWorkPostSuccess) { showTopSnackBar( Overlay.of(context), CustomSnackBar.success(message: "Everything went alright"), ); Navigator.pop(context); } else if (state is EduWorkFailed) { showTopSnackBar( Overlay.of(context), CustomSnackBar.error( message: translation.translation[state.msg]), ); } }, builder: (context, state) { if (state is EduWorkLoaded && state.isChanged) { return Padding( padding: const EdgeInsets.all(15.0), child: Center( child: UserDataSectionDesription( description: "Remember to save your changes", //TODO added to translation file, w8ting... ), ), ); } else { return SizedBox(); } }
BlocBuilder 代码
BlocBuilder<EduWorkBloc, EduWorkState>( builder: (context, state) { if (state is EduWorkLoading) { return Center( child: CircularProgressIndicator( color: Theme.of(context).colorScheme.secondary, )); } else if (state is EduWorkLoaded) { return ListView.builder( shrinkWrap: true, itemCount: selectedMap.length, itemBuilder: (context, index) { return Padding( padding: const EdgeInsets.all(8), child: EducationWidget( isSelected: state.eduWork.isSelectedEdu( selectedMap.values.elementAt(index).toString()), index: index, code: selectedMap.values.elementAt(index).toString(), name: selectedMap.keys.elementAt(index).toString(), onPressed: () { final codeName = selectedMap.values.elementAt(index).toString(); //if that level of education is selected unselect it if (state.eduWork.education.contains(codeName)) { state.eduWork.education.remove(codeName); BlocProvider.of<EduWorkBloc>(context) .add(ChangeEdu(state.eduWork)); //If unselected add it } else { state.eduWork.education.add(codeName); BlocProvider.of<EduWorkBloc>(context) .add(ChangeEdu(state.eduWork)); } }, ), ); }, ); } return Text('Error'); }, ),
解决方案
问题根源是BlocBuilder没有处理EduWorkFailed状态,当状态切换为EduWorkFailed时,会走到最后的return Text('Error')分支。同时要满足不重建ListView的需求,我们可以通过buildWhen参数过滤掉EduWorkFailed状态,让BlocBuilder在该状态下不重新构建,保持原有的ListView,同时由BlocListener处理snackbar的显示。
修改步骤:
- 给
BlocBuilder添加buildWhen参数,指定只有当状态为EduWorkLoading或EduWorkLoaded时才重新构建UI; - 确保
BlocListener已经正确处理EduWorkFailed状态的snackbar显示(这部分你已经实现)。
修改后的BlocBuilder代码:
BlocBuilder<EduWorkBloc, EduWorkState>( // 添加buildWhen,过滤EduWorkFailed状态,避免ListView重建 buildWhen: (previousState, currentState) { return currentState is EduWorkLoading || currentState is EduWorkLoaded; }, builder: (context, state) { if (state is EduWorkLoading) { return Center( child: CircularProgressIndicator( color: Theme.of(context).colorScheme.secondary, )); } else if (state is EduWorkLoaded) { return ListView.builder( shrinkWrap: true, itemCount: selectedMap.length, itemBuilder: (context, index) { return Padding( padding: const EdgeInsets.all(8), child: EducationWidget( isSelected: state.eduWork.isSelectedEdu( selectedMap.values.elementAt(index).toString()), index: index, code: selectedMap.values.elementAt(index).toString(), name: selectedMap.keys.elementAt(index).toString(), onPressed: () { final codeName = selectedMap.values.elementAt(index).toString(); if (state.eduWork.education.contains(codeName)) { state.eduWork.education.remove(codeName); BlocProvider.of<EduWorkBloc>(context) .add(ChangeEdu(state.eduWork)); } else { state.eduWork.education.add(codeName); BlocProvider.of<EduWorkBloc>(context) .add(ChangeEdu(state.eduWork)); } }, ), ); }, ); } // 这里的Error只有在初始状态非Loading/Loaded时才会触发,正常流程不会走到这里 return Text('Error'); }, ),
补充说明
如果你的业务需要在EduWorkFailed状态下更新ListView的某些UI(比如高亮错误项),可以修改EduWorkFailed状态类,让它携带之前的eduWork数据,然后在BlocBuilder中添加EduWorkFailed的处理分支,基于携带的数据渲染ListView,同时实现相应的错误UI逻辑。但如果只是单纯显示snackbar且不重建ListView,上述buildWhen的方案最简洁高效。
内容的提问来源于stack exchange,提问作者Beeednarek
相关产品推荐
相关产品推荐

