Flutter Clean Architecture中Bloc发射状态时触发异常排查
问题描述
我在Flutter Clean Architecture架构下使用Bloc,在GetTriviaForConcreteNumber事件中调用验证函数inputConventer.stringToUnsignedInteger()和用例getConcreteNumberTrivia(number),相关代码如下:
class NumberTriviaBloc extends Bloc<NumberTriviaEvent, NumberTriviaState> { final GetConcreteNumberTrivia getConcreteNumberTrivia; final InputConventer inputConventer; NumberTriviaBloc({ required this.getConcreteNumberTrivia, required this.inputConventer, }) : super(NumberTriviaInitial()) { on<GetTriviaForConcreteNumber>((event, emit) async { emit(NumberTriviaLoading()); final inputEither = inputConventer.stringToUnsignedInteger(event.number); inputEither.fold( (failureConvert) => emit(NumberTriviaError(failureConvert.message)), (number) async { final result = await getConcreteNumberTrivia(number); result.fold( (failure) => emit(NumberTriviaError(failure.message)), (trivia) => emit(NumberTriviaLoaded(trivia)), ); }, ); }); } }
运行时调用emit(NumberTriviaLoaded(trivia))触发异常,已尝试异常提示的解决方案但问题仍存在。预期状态序列应为[NumberTriviaLoading(), NumberTriviaError(failure.message)]或[NumberTriviaLoading(), NumberTriviaLoaded(trivia)],想知道遗漏了什么。
排查方向与解决方案
修复异步逻辑嵌套问题
你在inputEither.fold的成功分支嵌套了async函数,外层事件回调本身已是async,这种嵌套可能导致Bloc的状态发射上下文失效。可以将内层逻辑拉平,确保emit调用始终处于有效的事件处理上下文内:on<GetTriviaForConcreteNumber>((event, emit) async { emit(NumberTriviaLoading()); final inputEither = inputConventer.stringToUnsignedInteger(event.number); if (inputEither.isLeft()) { emit(NumberTriviaError(inputEither.left.message)); return; } final number = inputEither.right; final result = await getConcreteNumberTrivia(number); result.fold( (failure) => emit(NumberTriviaError(failure.message)), (trivia) => emit(NumberTriviaLoaded(trivia)), ); });确保状态类的不可变性
Bloc强制要求状态类必须是不可变的。检查NumberTriviaLoaded等状态类,确保所有字段都是final修饰,并且正确重写==和hashCode方法(或使用equatable包简化实现)。如果状态类可变,会导致状态发射时触发异常。捕获用例中的未处理异常
即使使用Either处理失败,getConcreteNumberTrivia内部仍可能抛出未被捕获的异常(比如网络请求崩溃),直接中断Bloc事件流程。添加try-catch确保所有异常都被转换为Failure:final number = inputEither.right; try { final result = await getConcreteNumberTrivia(number); result.fold( (failure) => emit(NumberTriviaError(failure.message)), (trivia) => emit(NumberTriviaLoaded(trivia)), ); } catch (e) { emit(NumberTriviaError('Unexpected error: ${e.toString()}')); }禁止事件并发处理
若同一事件被快速多次触发,前一个异步操作未完成时后一个事件开始发射状态,会引发状态冲突。注册事件时设置concurrent: false确保串行处理:on<GetTriviaForConcreteNumber>( (event, emit) async { // 原有逻辑 }, concurrent: false, );验证InputConventer的返回值有效性
确认stringToUnsignedInteger返回的Either成功分支是有效的无符号整数,没有返回null或非法值。若传递给用例的参数无效,后续流程的异常会间接导致emit失败。
内容的提问来源于stack exchange,提问作者Indra Trisna

