Search code examples
flutterdartflutter-blocflutter-cubit

List of checkbox not updating correctly on the UI (Flutter BloC)


I'm using the BloC pattern for the first time in my App. And I am having multiple of problems with it.

For my Question i have a list of boolean ( Checkboxes ) The problem is when I click on the boxes sometimes they updated ( the correct behavior ) sometimes they does not updated ( wrong behavior )

The check box does not change until I click multiple times

Cubit Class

class FilterCubit extends Cubit<FilterState> {
  FilterCubit() : super(const FilterInitial());

  int sortByGV = 0;

  void onSortByClick(int index) {
    sortByGV = index;
    emit(OnSortByState(index));
  }

  List<bool> choiceSportsList = [];

  void fillChoiceSportsList(int length) {
    for (int i = 0; i < length; i++) {
      choiceSportsList.add(false);
    }
  }

  void onChoiceSportClick(int index) {
    if (choiceSportsList[index]) {
      choiceSportsList[index] = false;
      emit(const OnChoiceSportState(false));
    } else {
      choiceSportsList[index] = true;
      emit(const OnChoiceSportState(true));
    }
  }
}

State Class

sealed class FilterState extends Equatable {
  const FilterState();

  @override
  List<Object?> get props => [];
}

final class FilterInitial extends FilterState {
  const FilterInitial();

  @override
  List<Object?> get props => [];
}

final class OnSortByState extends FilterState {
  final int index;
  const OnSortByState(this.index);

  @override
  List<Object?> get props => [index];
}

class OnChoiceSportState extends FilterState {
  final bool val;
  const OnChoiceSportState(this.val);

  @override
  List<Object?> get props => [val];
}

UI

showFilterBottomSheetComponent({double? heightFactor}) {
  sl<FilterCubit>().fillChoiceSportsList(10);

  return showModalBottomSheet<void>(
    context: sl<NavigationService>().navigatorKey.currentContext!,
    isScrollControlled: true,
    builder: (BuildContext context) {
      List<String> sports = [
        'Padel',
        'Soccer',
        'Basketball',
        'Tennis',
        'Padbol',
        'Pickleball',
        'Volleyball',
        'Badminton',
        'Squash',
        'Cricket'
      ];

      return FractionallySizedBox(
        heightFactor: heightFactor ?? 0.85,
        child: BlocProvider(
          create: (context) => sl<FilterCubit>(),
          child: SafeArea(
            child: Padding(
              padding:
                  EdgeInsets.all(AppPadding.p24).copyWith(top: AppPadding.p16),
              child: FilterChooseSportBody(data: sports),
            ),
          ),
        ),
      );
    },
  );
}

class FilterChooseSportBody extends StatelessWidget {
  const FilterChooseSportBody({
    super.key,
    required this.data,
  });

  final List<String> data;

  @override
  Widget build(BuildContext context) {
    final filterC = BlocProvider.of<FilterCubit>(context, listen: true);

    return Column(
      children: [
        for (int i = 0; i < data.length; i++)
          CheckBoxListTileComponent(
            title: data[i],
            value: filterC.choiceSportsList[i],
            onChanged: (value) {
              filterC.onChoiceSportClick(i);
            },
            smallHeight: true,
            style: getBodyMedium(),
          ),
      ],
    );
  }
}

In provider I have notifyListeners();

Do I need in bloc to create a state and emit it for each time??

Thanks.


Solution

  • I solved the problem with the following code.

    First i removed the state class and replace it with List

    Then the Cubit class like the following:

    List<String> sports = [
      'Padel',
      'Soccer',
      'Basketball',
      'Tennis',
      'Padbol',
      'Pickleball',
      'Volleyball',
      'Badminton',
      'Squash',
      'Cricket',
    ];
    
    class FilterCubit extends Cubit<List<FilterModel>> {
      FilterCubit() : super([]);
    
      void fillSport() {
        for (var e in sports) {
          final data = FilterModel(name: e, isActive: false);
          state.add(data);
          emit(state);
        }
      }
    
      void onChanged(Object? value, int index) {
        state[index].isActive = !state[index].isActive;
    
        emit([...state]);
      }
    }
    

    the UI widget

    class FilterChooseSportBody extends StatefulWidget {
      const FilterChooseSportBody({super.key});
    
      @override
      State<FilterChooseSportBody> createState() => _FilterChooseSportBodyState();
    }
    
    class _FilterChooseSportBodyState extends State<FilterChooseSportBody> {
      @override
      void initState() {
        if (sl<FilterCubit>().state.isEmpty) {
          sl<FilterCubit>().fillSport();
        }
    
        super.initState();
      }
    
      @override
      Widget build(BuildContext context) {
        return BlocBuilder<FilterCubit, List<FilterModel>>(
          builder: (context, state) {
            return Column(
              children: [
                for (int i = 0; i < state.length; i++)
                  CheckBoxListTileComponent(
                    title: sl<FilterCubit>().state[i].name,
                    value: state[i].isActive,
                    onChanged: (value) {
                      sl<FilterCubit>().onChanged(value, i);
                    },
                    smallHeight: true,
                    style: getBodyMedium(),
                  ),
              ],
            );
          },
        );
      }
    }
    

    and in the above widget tree i added BlocProvider.value not BlocProvider

    BlocProvider.value(value: cubit-here, child: .. )