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

Flutter Bloc中列表内对象更新后UI未变更的原因排查

为何Flutter Bloc中修改Todo的isDone后UI不更新?

我定义了一个Todo模型,同时有该模型可正常运行与无法运行的代码片段。点击复选框时,非工作版本里能看到isDone值在true和false间切换,但UI却未更新。我已经创建了新的tempList,理论上应该能触发UI更新,想知道问题出在哪?

Todo模型类

import 'dart:convert';

class ToDo {
  final String title;
  final String description;
  bool isDone;
  ToDo({
    required this.title,
    required this.description,
    required this.isDone,
  });

  ToDo copyWith({
    String? title,
    String? description,
    bool? isDone,
  }) {
    return ToDo(
      title: title ?? this.title,
      description: description ?? this.description,
      isDone: isDone ?? this.isDone,
    );
  }

  Map<String, dynamic> toMap() {
    return <String, dynamic>{
      'title': title,
      'description': description,
      'isDone': isDone,
    };
  }


  @override
  bool operator ==(covariant ToDo other) {
    if (identical(this, other)) return true;

    return other.title == title &&
        other.description == description &&
        other.isDone == isDone;
  }

  @override
  int get hashCode => title.hashCode ^ description.hashCode ^ isDone.hashCode;
}

可正常运行的代码

BlocBuilder中的代码

Checkbox(
  value: state.todoList[index].isDone,
  onChanged: (value) {
    context.read<TodoBloc>().add(UpdateToDo(
        todo: state.todoList[index].copyWith(
            isDone: !state.todoList[index].isDone),
        index: index));
  },
),

事件处理器中的代码

void _updateToDo(
    UpdateToDo event,
    Emitter<TodoState> emit,
  ) {
    try {
      final state = this.state;

      if (state is TodoLoaded) {
        List<ToDo> tasks = (state.todoList.map((task) {
          return task.title == event.todo.title ? event.todo : task;
        })).toList();
        emit(TodoLoaded(todoList: tasks));
      }
    } catch (e) {
      emit(TodoError(error: e.toString()));
    }
  }

无法运行的代码

BlocBuilder中的代码

Checkbox(
  value: state.todoList[index].isDone,
  onChanged: (value) {
    context.read<TodoBloc>().add(UpdateToDo(
        todo: state.todoList[index],
        index: index));
  },
),

事件处理器中的代码

void _updateToDo(
    UpdateToDo event,
    Emitter<TodoState> emit,
  ) {
    try {
      final state = this.state;
      if (state is TodoLoaded) {
        List<ToDo> tempList = List.from(state.todoList);
        for (int index = 0; index < tempList.length; index++) {
          if (index == event.index) {
            tempList[index].isDone = !tempList[index].isDone;
          }
        }
        emit(TodoLoaded(todoList: tempList));
      }
    } catch (e) {
      emit(TodoError(error: e.toString()));
    }
  }

问题原因分析

核心问题在于Bloc的不可变状态设计原则以及状态对比机制:

  1. 你创建了新的tempList,但列表中的Todo对象还是原状态里的引用。Bloc在判断是否需要更新UI时,会对比新旧TodoLoaded状态的相等性。虽然你修改了Todo的isDone属性,但由于列表元素是原对象引用,且Bloc依赖不可变状态变更来触发更新,直接修改对象属性属于可变操作,Bloc无法正确识别这是有效的状态变更。

  2. 正常运行的版本中,你通过copyWith创建了全新的Todo对象,替换了原列表中的元素。此时新列表的元素是新引用,TodoLoaded状态的todoList与旧状态的列表在元素层面完全不同,Bloc能准确识别状态变更,进而触发UI更新。

  3. 即使你创建了新列表,只要列表内的对象是原引用且被直接修改,Bloc的状态对比逻辑可能无法感知到状态的有效变化——因为Bloc默认会认为状态没有发生本质变更(列表引用变了,但元素引用未变,且你的==运算符虽然考虑了isDone,但Bloc的状态对比可能在列表层面判断时,由于元素引用相同而忽略了属性变化)。

总结:Bloc要求状态必须是不可变的,你必须通过创建新对象(而非修改现有对象属性)来生成新状态,这样Bloc才能正确对比新旧状态,触发UI更新。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 13:06:17