使用Flutter Bloc实现尺寸选择Toggle Button遇断言错误求助
问题描述
尝试将API返回的尺寸列表构建为单选Toggle Button组件,已编写ToggleSizeState、ToggleSizeEvent、ToggleSizeBloc及UI层代码,但运行时触发断言错误:
'package:flutter/src/material/toggle_buttons.dart': Failed assertion: line 198 pos 12: 'children.length == isSelected.length': is not true
期望实现单选尺寸选择效果,请求协助解决。
原代码片段
ToggleSizeState
//ToggleSizeState enum ToggleSizeStatus { initial, success, error, loading, selected } extension ToggleSizeStatusX on ToggleSizeStatus { bool get isInitial => this == ToggleSizeStatus.initial; bool get isSuccess => this == ToggleSizeStatus.success; bool get isError => this == ToggleSizeStatus.error; bool get isLoading => this == ToggleSizeStatus.loading; bool get isSelected => this == ToggleSizeStatus.selected; } class ToggleSizeState extends Equatable { const ToggleSizeState({ this.status = ToggleSizeStatus.initial, List<bool>? sizeSelection, int idSelected = 0, }) : sizeSelection = sizeSelection ?? const [], idSelected = idSelected; final List<bool> sizeSelection; final ToggleSizeStatus status; final int idSelected; @override List<Object> get props => [status, sizeSelection, idSelected]; ToggleSizeState copyWith({ List<bool>? sizeSelection, ToggleSizeStatus? status, int? idSelected, }) { return ToggleSizeState( sizeSelection: sizeSelection ?? this.sizeSelection, status: status ?? this.status, idSelected: idSelected ?? this.idSelected, ); } }
ToggleSizeEvent
abstract class ToggleSizeEvent extends Equatable { const ToggleSizeEvent(); @override List<Object?> get props => []; } class SelectSize extends ToggleSizeEvent { const SelectSize({ required this.idSelected, }); final int idSelected; @override List<Object?> get props => [idSelected]; }
ToggleSizeBloc
class ToggleSizeBloc extends Bloc<ToggleSizeEvent, ToggleSizeState> { final ProductModel product; ToggleSizeBloc({required this.product}) : super(const ToggleSizeState()) { on<SelectSize>(_mapSelectSizeEventToState); } void _mapSelectSizeEventToState( SelectSize event, Emitter<ToggleSizeState> emit) async { for (int index = 0; index < state.sizeSelection.length; index++) { if (index == index) { state.sizeSelection[index] = !state.sizeSelection[index]; } else { state.sizeSelection[index] = false; } } emit(state.copyWith( status: ToggleSizeStatus.selected, idSelected: event.idSelected, sizeSelection: List.generate(product.size!.length, (index) => false))); } }
ProductSize UI层
class ProductSize extends StatefulWidget { final ProductModel product; const ProductSize({super.key, required this.product}); @override State<ProductSize> createState() => _ProductSizeState(); } class _ProductSizeState extends State<ProductSize> { @override Widget build(BuildContext context) { return Container( color: Colors.white, child: Column( children: [ const Text('Please select size'), BlocBuilder<ToggleSizeBloc, ToggleSizeState>( builder: (context, state) { return ToggleButtons( selectedColor: Colors.white, color: pureBlack, fillColor: thistle, splashColor: lilac, highlightColor: lilac, textStyle: const TextStyle(fontWeight: FontWeight.bold), renderBorder: true, borderColor: lilac, borderWidth: 1.5, borderRadius: BorderRadius.circular(10), selectedBorderColor: lilac, isSelected: state.sizeSelection, onPressed: (int newIndex) { context .read<ToggleSizeBloc>() .add(SelectSize(idSelected: widget.product.id!)); }, children: [ for (var item in widget.product.size!) Padding( padding: const EdgeInsets.symmetric(horizontal: 12), child: Text(item, style: const TextStyle(fontSize: 18)), ), ], ); }, ), ], ), ); } }
问题原因分析
这个断言错误的核心是ToggleButtons的children数量和isSelected列表长度不匹配,具体问题点:
- 初始状态不匹配:
ToggleSizeState初始sizeSelection为空列表,而UI层children由product.size!生成,初始时两者长度不一致。 - Bloc逻辑错误:直接修改
state.sizeSelection(违反Bloc状态不可变原则),且每次触发事件时生成全false的列表,未正确标记选中项。 - UI事件传递错误:
onPressed传递产品ID而非点击的索引,导致无法定位选中的尺寸。
修正后的代码
1. ToggleSizeState(强制初始状态传入对应长度列表)
//ToggleSizeState enum ToggleSizeStatus { initial, success, error, loading, selected } extension ToggleSizeStatusX on ToggleSizeStatus { bool get isInitial => this == ToggleSizeStatus.initial; bool get isSuccess => this == ToggleSizeStatus.success; bool get isError => this == ToggleSizeStatus.error; bool get isLoading => this == ToggleSizeStatus.loading; bool get isSelected => this == ToggleSizeStatus.selected; } class ToggleSizeState extends Equatable { const ToggleSizeState({ this.status = ToggleSizeStatus.initial, required this.sizeSelection, this.idSelected = 0, }); final List<bool> sizeSelection; final ToggleSizeStatus status; final int idSelected; @override List<Object> get props => [status, sizeSelection, idSelected]; ToggleSizeState copyWith({ List<bool>? sizeSelection, ToggleSizeStatus? status, int? idSelected, }) { return ToggleSizeState( sizeSelection: sizeSelection ?? this.sizeSelection, status: status ?? this.status, idSelected: idSelected ?? this.idSelected, ); } }
2. ToggleSizeEvent(修改参数语义为选中索引)
abstract class ToggleSizeEvent extends Equatable { const ToggleSizeEvent(); @override List<Object?> get props => []; } class SelectSize extends ToggleSizeEvent { const SelectSize({ required this.selectedIndex, }); final int selectedIndex; @override List<Object?> get props => [selectedIndex]; }
3. ToggleSizeBloc(修正初始化和单选逻辑)
class ToggleSizeBloc extends Bloc<ToggleSizeEvent, ToggleSizeState> { final ProductModel product; ToggleSizeBloc({required this.product}) // 初始化时生成与尺寸列表长度一致的全未选中状态 : super(ToggleSizeState( sizeSelection: List.generate(product.size!.length, (_) => false), )) { on<SelectSize>(_mapSelectSizeEventToState); } void _mapSelectSizeEventToState( SelectSize event, Emitter<ToggleSizeState> emit) async { // 创建新列表避免修改原状态 final newSizeSelection = List<bool>.from(state.sizeSelection); // 单选逻辑:先全部置为false,再标记选中索引为true for (int i = 0; i < newSizeSelection.length; i++) { newSizeSelection[i] = i == event.selectedIndex; } emit(state.copyWith( status: ToggleSizeStatus.selected, idSelected: product.id!, sizeSelection: newSizeSelection, )); } }
4. ProductSize UI层(传递正确的点击索引)
class ProductSize extends StatefulWidget { final ProductModel product; const ProductSize({super.key, required this.product}); @override State<ProductSize> createState() => _ProductSizeState(); } class _ProductSizeState extends State<ProductSize> { @override Widget build(BuildContext context) { return Container( color: Colors.white, child: Column( children: [ const Text('Please select size'), BlocBuilder<ToggleSizeBloc, ToggleSizeState>( builder: (context, state) { return ToggleButtons( selectedColor: Colors.white, color: pureBlack, fillColor: thistle, splashColor: lilac, highlightColor: lilac, textStyle: const TextStyle(fontWeight: FontWeight.bold), renderBorder: true, borderColor: lilac, borderWidth: 1.5, borderRadius: BorderRadius.circular(10), selectedBorderColor: lilac, isSelected: state.sizeSelection, onPressed: (int newIndex) { // 传递点击的索引给Bloc context .read<ToggleSizeBloc>() .add(SelectSize(selectedIndex: newIndex)); }, children: [ for (var item in widget.product.size!) Padding( padding: const EdgeInsets.symmetric(horizontal: 12), child: Text(item, style: const TextStyle(fontSize: 18)), ), ], ); }, ), ], ), ); } }
关键修改总结
- 初始状态对齐:Bloc初始化时根据尺寸列表长度生成对应长度的选择状态,保证与UI层
children数量一致。 - 遵循状态不可变:事件处理中创建新列表修改状态,不直接操作原状态。
- 修正单选逻辑:实现“先全部取消,再选中当前项”的单选效果。
- 事件参数修正:UI层传递点击索引给Bloc,确保正确定位选中项。
内容的提问来源于stack exchange,提问作者pythonGo
相关产品推荐
相关产品推荐

