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

Flutter BLoC提交时List状态丢失问题求助

问题描述

使用Flutter BLoC管理状态,ExamState中存储List<Map<String, dynamic>>类型的scores。提交数据时state.scores为空,但在HandleScoreInputEvent事件处理中能正常访问已设置的状态,已排查三天未解决。

相关代码

State代码

class ExamState {
  final List<Map<String, dynamic>> scores;

  ExamState({
    this.scores = const [],
  });
  ExamState copyWith({
    List<Map<String, dynamic>> scores = const [],
  }) {
    return ExamState(
      scores: scores,
    );
  }
}

ExamBloc事件处理代码

List<Map<String, dynamic>>? scores;

class ExamBloc extends Bloc<ExamEvent, ExamState> {
  final ExamRepository? repo = ExamRepository();

  ExamBloc() : super(ExamState()) {
    on<HandleScoreInputEvent>((event, emit) {
      final existingIndex = state.scores
          .indexWhere((score) => score['studentId'] == event.studentId);

      if (existingIndex != -1) {
        final updatedScore = {
          ...state.scores[existingIndex],
          if (event.marks != null) 'marks': event.marks,
          if (event.comment != null) 'comment': event.comment,
        };

        final List<Map<String, dynamic>> updatedScores =
            List.from(state.scores);

        updatedScores[existingIndex] = updatedScore;
        emit(state.copyWith(scores: updatedScores));
        scores = updatedScores;
      } else {
        final score = {
          'studentId': event.studentId,
          'classId': event.classId,
          'examId': event.examId,
          'subjectId': event.subjectId,
          if (event.marks != null) 'marks': event.marks,
          if (event.comment != null) 'comment': event.comment,
        };
        final updatedScores = [...state.scores, score];
        updatedScores.add(score);

        emit(state.copyWith(scores: updatedScores));
      }
    });

    on<ShowExamEvent>((event, emit) async {
      emit(state.copyWith(formStatus: FormSubmitting()));
      try {
        final res = await repo!.showExam(examId: event.examId);
        if (res.statusCode == 200) {
          var responseJson = jsonDecode(res.body);
          ExamModel exam = ExamModel.fromJson(responseJson);
          emit(state.copyWith(exam: exam));
          emit(state.copyWith(formStatus: SubmissionSuccess()));
        } else {
          emit(state.copyWith(
              formStatus: ApiRequestNotSuccessFul(res.body.toString())));
        }
      } catch (e) {
        print(e);
        emit(state.copyWith(formStatus: SubmissionFailed(e as Exception)));
      }
    });

    on<SubmitStudentExamResult>((event, emit) async {
      emit(state.copyWith(formStatus: FormSubmitting()));
      print(state.scores); // 此处为空

      try {
        final res = await repo!.saveExamResult(
          data: scores,
        );
        if (res.statusCode == 201) {
          emit(state.copyWith(formStatus: SubmissionSuccess()));
        } else {
          emit(state.copyWith(
              formStatus: ApiRequestNotSuccessFul(res.body.toString())));
        }
      } catch (e) {
        emit(state.copyWith(formStatus: SubmissionFailed(e as Exception)));
      }
    });
  }
}

视图层防抖输入代码

final _debounceSubject = BehaviorSubject<String>();

@override
void initState() {
  super.initState();

  _debounceSubject.stream
      .debounceTime(
          Duration(milliseconds: 700)) // 可调整防抖时间
      .listen((value) {
    // 处理防抖后的值(例如更新scores)
    // 仅当_currentStudentId可用时调用_handleMarksChange
    _handleMarksChange(
      value,
      // 此场景无评论
    );
  });
}

List scores = [];
String? _currentStudentId;

void _handleMarksChange(String value) {
  // 更新BLoC状态中的新分数值
  int? marksValue = int.tryParse(value);
  if (marksValue != null) {
    // 输入为分数数字
    context.read<ExamBloc>().add(HandleScoreInputEvent(
        marks: int.parse(value),
        studentId: _currentStudentId!,
        classId: widget.classId,
        examId: widget.examId,
        subjectId: widget.subjectId,
        comment: null));
  } else {
    context.read<ExamBloc>().add(HandleScoreInputEvent(
        marks: null,
        studentId: _currentStudentId!,
        classId: widget.classId,
        examId: widget.examId,
        subjectId: widget.subjectId,
        comment: value));
  }
}

Widget _submitButton(BuildContext context) {
  return BlocBuilder<ExamBloc, ExamState>(builder: (context, state) {
    return PrimaryButton(
      onPressed: () {
        if (_key.currentState!.validate()) {
          context.read<ExamBloc>().add(SubmitStudentExamResult());
        }
      },
      label: state.formStatus is FormSubmitting
          ? CircularProgressIndicator(
              valueColor: const AlwaysStoppedAnimation<Color>(Colors.white),
            )
          : const Text('Save'),
    );
  });
}
问题根源
  • copyWith参数默认值错误:ExamState的copyWith方法中,scores默认值设为const [],导致调用copyWith不传入scores时,直接将新状态的scores重置为空数组(比如ShowExamEvent中更新exam时)。
  • 冗余的add操作:HandleScoreInputEvent的else分支中,[...state.scores, score]已经完成新数据添加,又调用updatedScores.add(score),导致数据重复,且状态更新逻辑混乱。
  • 违规使用全局变量:Bloc外部定义的scores变量不符合BLoC状态管理规范,且仅在HandleScoreInputEvent的if分支赋值,else分支未处理,提交时依赖该变量而非state数据。
修复方案

1. 修复ExamState的copyWith方法

将参数默认值改为null,确保未传入时沿用当前状态的属性:

class ExamState {
  final List<Map<String, dynamic>> scores;
  final ExamModel? exam;
  final FormStatus formStatus;

  ExamState({
    this.scores = const [],
    this.exam,
    this.formStatus = const InitialFormStatus(),
  });

  ExamState copyWith({
    List<Map<String, dynamic>>? scores,
    ExamModel? exam,
    FormStatus? formStatus,
  }) {
    return ExamState(
      scores: scores ?? this.scores,
      exam: exam ?? this.exam,
      formStatus: formStatus ?? this.formStatus,
    );
  }
}

2. 修正HandleScoreInputEvent逻辑

移除多余的add操作,删除全局变量赋值:

on<HandleScoreInputEvent>((event, emit) {
  final existingIndex = state.scores
      .indexWhere((score) => score['studentId'] == event.studentId);

  if (existingIndex != -1) {
    final updatedScore = {
      ...state.scores[existingIndex],
      if (event.marks != null) 'marks': event.marks,
      if (event.comment != null) 'comment': event.comment,
    };

    final List<Map<String, dynamic>> updatedScores = List.from(state.scores);
    updatedScores[existingIndex] = updatedScore;
    emit(state.copyWith(scores: updatedScores));
  } else {
    final score = {
      'studentId': event.studentId,
      'classId': event.classId,
      'examId': event.examId,
      'subjectId': event.subjectId,
      if (event.marks != null) 'marks': event.marks,
      if (event.comment != null) 'comment': event.comment,
    };
    final updatedScores = [...state.scores, score];
    emit(state.copyWith(scores: updatedScores));
  }
});

3. 修复提交事件逻辑

删除全局scores变量,直接使用state.scores提交数据:

on<SubmitStudentExamResult>((event, emit) async {
  emit(state.copyWith(formStatus: const FormSubmitting()));
  print(state.scores); // 现在可正常获取数据

  try {
    final res = await repo!.saveExamResult(
      data: state.scores,
    );
    if (res.statusCode == 201) {
      emit(state.copyWith(formStatus: const SubmissionSuccess()));
    } else {
      emit(state.copyWith(
          formStatus: ApiRequestNotSuccessFul(res.body.toString())));
    }
  } catch (e) {
    emit(state.copyWith(formStatus: SubmissionFailed(e as Exception)));
  }
});

内容的提问来源于stack exchange,提问作者wafutech

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 15:39:52