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

使用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列表长度不匹配,具体问题点:

  1. 初始状态不匹配:ToggleSizeState初始sizeSelection为空列表,而UI层children由product.size!生成,初始时两者长度不一致。
  2. Bloc逻辑错误:直接修改state.sizeSelection(违反Bloc状态不可变原则),且每次触发事件时生成全false的列表,未正确标记选中项。
  3. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 13:01:19