Flutter Bloc状态不更新问题:点击答案按钮后状态未更新但模型结果已变
问题描述
我正在学习Flutter和Bloc框架,开发了一个简易的限时数学测验应用,其中包含处理数学运算的CalcModel模型。点击生成数字的CalcResult事件时,CalcState能正常获取随机数,但点击答案按钮触发NumberPressed事件时,CalcState无法更新,不过终端打印显示模型的result字段已经改变。
相关代码
Bloc代码
class CalcBloc extends Bloc<CalcEvent, CalcState> { CalcBloc() : super(const CalcInitial()) { on<CalcResult>(_onGetNumbers); on<NumberPressed>(_onNumberPressed); } Random random = Random(); var list = ['+', '-']; bool check = false; void _onGetNumbers(CalcResult event, Emitter<CalcState> emit) { int randomNumber1 = random.nextInt(100); int randomNumber2 = random.nextInt(100); var element = list[random.nextInt(list.length)]; bool check = true; if (check) { print('true'); } CalcModel model = state.calcModel.copyWith( firstOperand: randomNumber1, firstOperator: element, secondOperand: randomNumber2, result: null); emit(CalcState(calcModel: model)); print(model); } void _onNumberPressed(NumberPressed event, Emitter<CalcState> emit) { final state = this.state; CalcModel model = state.calcModel.copyWith( firstOperand: state.calcModel.firstOperand, firstOperator: state.calcModel.firstOperator, secondOperand: state.calcModel.secondOperand, result: event.number); emit(CalcState(calcModel: model)); print(model); } }
State代码
class CalcState extends Equatable { final CalcModel calcModel; const CalcState({this.calcModel = const CalcModel()}); @override List<Object> get props => [calcModel]; } class CalcInitial extends CalcState { const CalcInitial() : super(calcModel: const CalcModel()); }
Event代码
abstract class CalcEvent extends Equatable { const CalcEvent(); @override List<Object> get props => []; } class CalcResult extends CalcEvent { @override List<Object> get props => []; } class NumberPressed extends CalcEvent { final int number; const NumberPressed({required this.number}); @override List<Object> get props => [number]; }
页面代码
class TestingScreen extends StatefulWidget { const TestingScreen({Key? key}) : super(key: key); @override State<TestingScreen> createState() => _TestingScreenState(); } class _TestingScreenState extends State<TestingScreen> { @override @override Widget build(BuildContext context) { return BlocBuilder<CalcBloc, CalcState>( builder: (context, state) { return Scaffold( body: Column( children: [ const SizedBox( height: 100, ), Center(child: state.calcModel.firstOperand == null ? const Text('') : Text(state.calcModel.toString())), const SizedBox( height: 100, ), Padding( padding: const EdgeInsets.symmetric(horizontal: 80, vertical: 40), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ _numberbutton(text: '7', onTap: () => numberPressed(7)), _numberbutton(text: '8', onTap: () => numberPressed(8)), _numberbutton(text: '9', onTap: () => numberPressed(9)), ], ), ), Padding( padding: const EdgeInsets.symmetric(horizontal: 80, vertical: 40), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ _numberbutton(text: '4', onTap: () => numberPressed(4)), _numberbutton(text: '5', onTap: () => numberPressed(5)), _numberbutton(text: '6', onTap: () => numberPressed(6)), ], ), ), Padding( padding: const EdgeInsets.symmetric(horizontal: 80, vertical: 40), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ _numberbutton(text: '1', onTap: () => numberPressed(1)), _numberbutton(text: '2', onTap: () => numberPressed(2)), _numberbutton(text: '3', onTap: () => numberPressed(3)), ], ), ), Padding( padding: const EdgeInsets.symmetric(horizontal: 80, vertical: 40), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ _numberbutton(text: '0', onTap: () => numberPressed(0)), _numberbutton( text: '=', onTap: () { context.read<CalcBloc>().add(CalcResult()); }), ], ), ), ], ), ); }, ); } Widget _numberbutton({ required String text, required VoidCallback onTap, }) { return InkWell( onTap: onTap, child: Text(text), ); } void numberPressed(int number) { context.read<CalcBloc>().add(NumberPressed(number: number)); } }
依赖版本
- flutter_bloc: ^8.0.1
- bloc: ^8.0.3
- equatable: ^2.0.3
CalcModel代码
import 'package:equatable/equatable.dart'; class CalcModel extends Equatable { final int? firstOperand; final String? firstOperator; final int? secondOperand; final String? secondOperator; final int? thirdOperand; final String? thirdOperator; final int? fourthOperand; final int? result; const CalcModel( {this.firstOperand, this.firstOperator, this.secondOperand, this.secondOperator, this.thirdOperand, this.thirdOperator, this.fourthOperand, this.result}); CalcModel copyWith({ int? firstOperand, String? firstOperator, int? secondOperand, String? secondOperator, int? thirdOperand, String? thirdOperator, int? fourthOperand, int? result, }) { return CalcModel( firstOperand: firstOperand ?? this.firstOperand, firstOperator: firstOperator ?? this.firstOperator, secondOperand: secondOperand ?? this.secondOperand, secondOperator: secondOperator ?? this.secondOperator, thirdOperand: thirdOperand ?? this.thirdOperand, thirdOperator: thirdOperator ?? this.thirdOperator, fourthOperand: fourthOperand ?? this.fourthOperand, result: result ?? this.result); } CalcModel.fromJson(Map<String, dynamic> json) : firstOperand = json['firstOperand'] as int?, firstOperator = json['firstOperator'] as String?, secondOperand = json['secondOperand'] as int?, secondOperator = json['secondOperator'] as String?, thirdOperand = json['thirdOperand'] as int?, thirdOperator = json['thirdOperator'] as String?, fourthOperand = json['fourthOperand'] as int?, result = json['result'] as int?; Map<String, dynamic> toJson() => { 'firstOperand': firstOperand, 'firstOperator': firstOperator, 'secondOperand': secondOperand, 'secondOperator': secondOperator, 'thirdOperand': thirdOperand, 'thirdOperator': thirdOperator, 'fourthOperand': fourthOperand, 'result': result, }; @override String toString() { return '$firstOperand $firstOperator $secondOperand = $result'; } @override List<Object?> get props => [ firstOperand, firstOperator, secondOperand, secondOperator, thirdOperand, thirdOperator, fourthOperand ]; }
解决方案
问题根源在于CalcModel的props列表未包含result字段。Equatable通过props列表中的字段来判断对象是否相等,当你更新result字段时,由于该字段不在props列表中,Equatable会判定新旧CalcModel对象相等,导致Bloc认为状态没有变化,不会触发BlocBuilder的UI重建。
修复代码
修改CalcModel的props列表,添加result字段:
@override List<Object?> get props => [ firstOperand, firstOperator, secondOperand, secondOperator, thirdOperand, thirdOperator, fourthOperand, result // 新增该行 ];
修改后,当result字段变化时,Equatable会识别出CalcModel对象已更新,Bloc会正常触发状态更新,UI也会随之刷新。
内容的提问来源于stack exchange,提问作者kazekkloss
相关产品推荐
相关产品推荐

