Flutter使用Freezed+Bloc时无法修改不可变列表问题求助
问题描述
修改列表元素时抛出错误:Can't modify list because of Unsupported operation: Cannot modify an unmodifiable list
技术栈:Bloc做状态管理,Freezed做代码生成,相关代码如下:
服务端获取角色绑定需求列表逻辑
Future<RoleWithNeeds> getRoleWithNeeds({ required RoleWithNeeds selectedRoles, }) async { final dataFromApi = await _apiClient.fetchNeedsByRoles(selectedRoles.idRole); final needsApi = dataFromApi.data; final needsRepository = <Need>[]; // convert to a repository model for (var i = 0; i < needsApi.length; i++) { needsRepository.add(Need( id: needsApi[i].id, name: needsApi[i].name, createdAt: needsApi[i].createdAt, )); } return selectedRoles.copyWith(needs: needsRepository); }
修改RoleWithNeeds嵌套数组逻辑
FutureOr<void> _onClickNeed(_ClickNeed event, Emitter<NeedState> emit) { emit(_Load()); try { for (var role in _listRoleWithNeeds) { for (var need in role.needs) { if (need == event.need) { final changedNeed = event.need.copyWith(isSelected: !event.need.isSelected); _listRoleWithNeeds[_listRoleWithNeeds .indexWhere((element) => element.needs.contains(need))] .needs[_listRoleWithNeeds[_listRoleWithNeeds .indexWhere((element) => element.needs.contains(need))] .needs .indexOf(event.need)] = changedNeed; if (_searchedList.contains(event.need)) { _searchedList[_searchedList.indexOf(event.need)] = changedNeed; } break; } } } _selectedList.clear(); for (final roleWithNeeds in _listRoleWithNeeds) { for (final need in roleWithNeeds.needs) { if (need.isSelected){ _selectedList.add(need); } } } if(_isSelectedButtonPressed){ emit(_Search(needs: _selectedList, sum: _selectedList.length)); } else{ !_isSearched ? emit(_Success(model: _listRoleWithNeeds, sum: _selectedList.length)) : emit(_Search(needs: _searchedList, sum: _selectedList.length)); } } catch (e) { emit(_Error(e.toString())); } }
模型定义
@freezed class RoleWithNeeds with _$RoleWithNeeds { factory RoleWithNeeds({ required int idRole, required String nameRole, required String svgUrl, @Default(true) bool isShort, @Default(<Need>[]) List<Need> needs, @Default(false) bool isSelected, }) = _RoleWithNeeds; factory RoleWithNeeds.fromJson(Map<String, dynamic> json) => _$RoleWithNeedsFromJson(json); } @freezed class Need with _$Need { factory Need({ required int id, required String name, @Default(false) bool isSelected, String? createdAt, }) = _Need; factory Need.fromJson(Map<String, dynamic> json) => _$NeedFromJson(json); }
错误原因
Freezed生成的数据类默认是不可变的,所有集合类型(List/Map/Set)属性会被自动包装为List.unmodifiable()返回的不可修改视图,不支持下标赋值、add、remove等修改操作。
现有代码直接通过多层下标索引修改role.needs的内部元素,本质是在修改不可变对象的只读属性,必然触发该异常。同时原有逻辑反复调用indexWhere查找元素下标,代码冗余且容易出现索引匹配错误。
修复方案
遵循Bloc和Freezed的不可变设计原则,所有属性修改都通过copyWith生成新的实例替换原有对象,不要直接修改原有实例的内部属性。
替换原有_onClickNeed方法中的列表修改逻辑为以下实现:
FutureOr<void> _onClickNeed(_ClickNeed event, Emitter<NeedState> emit) { emit(_Load()); try { // 遍历生成新的角色列表,不修改原有对象 final updatedRoleList = _listRoleWithNeeds.map((role) { // 查找当前角色下匹配的需求下标 final targetNeedIndex = role.needs.indexWhere((item) => item == event.need); // 无匹配项直接返回原角色实例 if (targetNeedIndex == -1) return role; // 复制生成新的可修改需求列表,替换目标项 final updatedNeeds = List<Need>.from(role.needs); updatedNeeds[targetNeedIndex] = event.need.copyWith(isSelected: !event.need.isSelected); // 生成新的角色实例替换旧实例 return role.copyWith(needs: updatedNeeds); }).toList(); _listRoleWithNeeds = updatedRoleList; // 同步更新搜索列表 final targetSearchedIndex = _searchedList.indexWhere((item) => item == event.need); if (targetSearchedIndex != -1) { final updatedSearchedList = List<Need>.from(_searchedList); updatedSearchedList[targetSearchedIndex] = event.need.copyWith(isSelected: !event.need.isSelected); _searchedList = updatedSearchedList; } // 重新计算选中列表 _selectedList.clear(); for (final roleWithNeeds in _listRoleWithNeeds) { for (final need in roleWithNeeds.needs) { if (need.isSelected) { _selectedList.add(need); } } } // 原有状态发射逻辑保持不变 if (_isSelectedButtonPressed) { emit(_Search(needs: _selectedList, sum: _selectedList.length)); } else { !_isSearched ? emit(_Success(model: _listRoleWithNeeds, sum: _selectedList.length)) : emit(_Search(needs: _searchedList, sum: _selectedList.length)); } } catch (e) { emit(_Error(e.toString())); } }
如果确实需要直接修改集合属性(不推荐,会破坏状态可追踪性,和Bloc的不可变状态设计冲突),可以给对应集合属性添加@unfreezed注解,让Freezed不包装为不可修改列表:
@freezed class RoleWithNeeds with _$RoleWithNeeds { factory RoleWithNeeds({ required int idRole, required String nameRole, required String svgUrl, @Default(true) bool isShort, @unfreezed @Default(<Need>[]) List<Need> needs, @Default(false) bool isSelected, }) = _RoleWithNeeds; factory RoleWithNeeds.fromJson(Map<String, dynamic> json) => _$RoleWithNeedsFromJson(json); }
内容的提问来源于stack exchange,提问作者veesilenavi
相关产品推荐
相关产品推荐

