You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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的显示。

修改步骤:

  1. 给BlocBuilder添加buildWhen参数,指定只有当状态为EduWorkLoading或EduWorkLoaded时才重新构建UI;
  2. 确保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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 20:57:09