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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 03:45:01