0

I am using DropdownSearch with lazy loading inside a modal bottom sheet in Flutter. The list of items is fetched using Bloc (LocationMasterCubit), and I am handling infinite scrolling to load more items dynamically.

The problem is that when I call setState(() {}); after fetching more items, the dropdown list does not update correctly. It seems like setState is not triggering a rebuild for the dropdown items.

Code Snippet: Here’s how I implemented the lazy loading inside DropdownSearch:

    BlocBuilder<LocationMasterCubit, LocationMasterState>(
  builder: (context, state) {
    return DropdownSearch<String>(
      suffixProps: DropdownSuffixProps(
        dropdownButtonProps: DropdownButtonProps(
          iconClosed: const Icon(Icons.keyboard_arrow_down),
          iconOpened: const Icon(Icons.keyboard_arrow_up),
          iconSize: 20.sp,
          color: ColorManager.black,
        ),
      ),
      selectedItem: locationMaster?.costCenter1 ?? costCenter1,
      decoratorProps: const DropDownDecoratorProps(
        decoration: InputDecoration(labelText: 'Cost Center 1'),
      ),
      items: (f, loadProps) => locationMasterCubit.costCenter1?.result ?? [],
      itemAsString: (item) => item,
      onChanged: (selectedItem) {
        setState(() {
          costCenter1 = selectedItem!;
        });
      },
      popupProps: PopupProps.menu(
        itemBuilder: (context, item, isDisabled, isSelected) {
          return ListTile(
            title: Text(item),
          );
        },
        constraints: BoxConstraints(maxHeight: 150.h),
        listViewProps: ListViewProps(
          controller: scrollController,
        ),
        searchFieldProps: TextFieldProps(
          decoration: const InputDecoration(hintText: 'Search...'),
          onChanged: (value) {
            locationMasterCubit.getCostCenter1(search: value, page: 1);
          },
        ),
        showSearchBox: true,
      ),
    );
  },
);

ScrollController (Handling Pagination):

scrollController.addListener(() async {
  if (scrollController.position.pixels >=
      scrollController.position.maxScrollExtent) {
    currentPage++;
    await locationMasterCubit.getCostCenter1(page: currentPage);
    setState(() {}); // Not updating the dropdown items
  }
});

Issue: When I scroll to the bottom, getCostCenter1(page: currentPage) fetches more items successfully.

However, DropdownSearch does not update to show the newly fetched items.

setState(() {}) is called, but it does not trigger a rebuild.

Cubit logic:

Future<void> getCostCenter1({String? search, required int page}) async {
    try {
      if (!isSearching) {
        isSearching = true;
        page == 1 ? emit(_FetchingData()) : emit(_FetchingNextData());
        if (page == 1) costCenter1 = null;
        final response = await locationMasterApiService.getCostCenter1(
            search: search, page: page);
        final costCenter1List = <String>[
          if (costCenter1 != null) ...costCenter1!.result,
          ...response['items'].map((cost) => cost)
        ];
        costCenter1 =
            PaginatedResponse<String>.fromJson(response, costCenter1List);
        isSearching = false;
        emit(_FetchedData());
      }
    } on ApiException catch (e) {
      isSearching = false;
      emit(
        _LocationMasterFailed(
          message: e.maybeWhen(
            connectionException: (message, context) => message,
            basic: (message, context) => message,
            orElse: () => e.context!,
          ),
        ),
      );
    }
  }

Question:

How can I make sure DropdownSearch updates properly when new items are fetched? Should I use StatefulBuilder, BlocListener, or another approach?

Any help would be appreciated!

3
  • I don't think you should be using setState if you have the Cubits managing the state. For a bit more clarification, could you edit it to add the getCostCenter1 event handler. Commented Mar 25 at 18:02
  • so i added for you the event handler but i found somehow a solutions that works (see the second solution), waiting for a better solution. thank you! Commented Mar 26 at 8:22
  • @elvril Why is the _FetchedData state empty? it should have the fields (literally, the state) to hold data. looking back on the code, I realized that you are not using the state at all. There is no need to setState if you are using BlocBuilder correct. The Cubit should emit states, and the BlocBuilder should use the state while building the widget. While emiting a new state, the BlocBuilder will also reactively rebuild. Please see some example apps that use Cubit. Commented Mar 26 at 10:26

2 Answers 2

0

setState(() {}) only triggers a rebuild for the widget it is inside, but DropdownSearch is getting its items from locationMasterCubit.costCenter1?.result ?? [], which is outside setState’s scope.

Instead of relying on setState, use BlocListener to trigger UI updates when the costCenter1 list is updated.

BlocConsumer<LocationMasterCubit, LocationMasterState>(
  listener: (context, state) {
    // When new items are fetched, trigger UI update
    if (state is LocationMasterLoaded) {
      setState(() {}); // Forces dropdown to refresh with new data
    }
  },
builder: (context, state) {
    return DropdownSearch<String>(
      suffixProps: DropdownSuffixProps(
        dropdownButtonProps: DropdownButtonProps(
          iconClosed: const Icon(Icons.keyboard_arrow_down),
          iconOpened: const Icon(Icons.keyboard_arrow_up),
          iconSize: 20.sp,
          color: ColorManager.black,
        ),
      ),

........ and more............

Removed setState(() {}) from scrollController.addListener: The UI will now rebuild based on Bloc state changes instead.

Sign up to request clarification or add additional context in comments.

1 Comment

it didn't work but your answer helped me to figure somehow a solution so thank you
0

Initially, I thought the problem was due to incorrect usage of setState, but after checking the logs and read the code of the package, I found that the package itself does not automatically refresh the dropdown items when the state changes.

The dropdown_search package has lazy loading with skip and take parameters, but my data structure wasn’t suitable for using skip. After extensive research, I found a workaround by using a key and manually calling reloadItems().

By adding a GlobalKey<DropdownSearchState<String>> and calling reloadItems() inside BlocListener, the dropdown correctly updates when the Cubit fetches new data.

final dropdownKey = GlobalKey<DropdownSearchState<String>>();

BlocListener<LocationMasterCubit, LocationMasterState>(
  listener: (context, state) {
    state.whenOrNull(fetchedData: () {
      dropdownKey.currentState?.getPopupState?.reloadItems('');
    });
  },
  child: DropdownSearch<String>(
    key: dropdownKey, // Assign the key here
    suffixProps: DropdownSuffixProps(
      dropdownButtonProps: DropdownButtonProps(
        iconClosed: const Icon(Icons.keyboard_arrow_down),
        iconOpened: const Icon(Icons.keyboard_arrow_up),
        iconSize: 20.sp,
        color: ColorManager.black,
      ),
    ),
    selectedItem: locationMaster?.costCenter1 ?? costCenter1,
    decoratorProps: const DropDownDecoratorProps(
      decoration: InputDecoration(labelText: 'Cost Center 1'),
    ),
    items: (f, loadProps) => locationMasterCubit.costCenter1?.result ?? [],
    itemAsString: (item) => item,
    onSelected: (selectedItem) {
      setState(() {
        costCenter1 = selectedItem!;
      });
    },
    validator: (value) => (value == null || value.isEmpty) ? "Required field" : null,
    popupProps: PopupProps.menu(
      constraints: BoxConstraints(maxHeight: 150.h),
      listViewProps: ListViewProps(
        controller: scrollController,
      ),
      searchFieldProps: TextFieldProps(
        decoration: const InputDecoration(hintText: 'Search...'),
        onSelected: (value) {
          locationMasterCubit.getCostCenter1(search: value, page: 1);
        },
      ),
      showSearchBox: true,
    ),
  ),
);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.