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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 17:54:33