Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

I am using dropdown searchable but searchable is list is come from network call #104

Closed
radheyshyamjat opened this issue Nov 29, 2022 · 22 comments
Labels
invalid This doesn't seem right

Comments

@radheyshyamjat
Copy link

Passing selected items as a constructor but it will be not working. Normal situation it will be work. When we make this one as a reusable widget then it will be not work selected value is not setted.

Below sample code added

  Future<void> searchModality(String searchKey) async {
/// Network call
    List<String> list = await getModality(context, searchKey);
    commonProvider.updateModality(CustomObj.toMap(list));
    print("List $list");
    commonProvider.refreshWidget();
  }

Consumer<CommonWidgetRefreshProvider>(
              builder: (context, value, child) {
            return Container(
              width: 130,
              child: CustomSearchableDialog(
                finalValueSelection: (p0) {
                  commonProvider.updateSelectedModality(p0);
                },
                dropDownValue: commonProvider.modalityList,
                searchCallBack: (searchKey) {
                  searchModality(searchKey);
                },
                selectedItems: commonProvider.selectedItemsModality,
              ),
            );
})
class CustomSearchableDialog extends StatefulWidget {
  final double searchFontSize;
  final FontWeight searchFontWeight;
  final String searchFontFamily;
  final Color searchTextColor;

  final String hintSearch;
  final double hintSearchFontSize;
  final FontWeight hintSearchFontWeight;
  final String hintSearchFontFamily;
  final Color hintSearchTextColor;

  final Function(String)? searchCallBack;
  final Function(List<dynamic>)? finalValueSelection;
  final List<dynamic> dropDownValue;
  final List<dynamic> selectedItems;

  final Color listItemTextColor;
  final double listItemFontSize;
  final FontWeight listItemFontWeight;
  final String listItemFontFamily;

  final Color checkMarkColor;
  final double checkMarkSize;

  const CustomSearchableDialog(
      {this.hintSearch = "Search for",
      this.hintSearchFontSize = FONT_SIZE_REGULAR,
      this.hintSearchFontWeight = FontWeight.w400,
      this.hintSearchFontFamily = MONTSERRAT_REGULAR,
      this.hintSearchTextColor = black11,
      this.searchFontSize = FONT_SIZE_REGULAR,
      this.searchFontWeight = FontWeight.w400,
      this.searchFontFamily = MONTSERRAT_REGULAR,
      this.searchTextColor = GREY_SHADE,
      this.listItemTextColor = ASSIGNEE_COLOR,
      this.listItemFontSize = FONT_SIZE_REGULAR,
      this.listItemFontWeight = FontWeight.w400,
      this.listItemFontFamily = MONTSERRAT_REGULAR,
      this.checkMarkColor = darkblue,
      this.checkMarkSize = 16,
      this.dropDownValue = const [],
      this.selectedItems = const [],
      this.finalValueSelection,
      this.searchCallBack,
      Key? key})
      : super(key: key);

  @override
  _CustomSearchableDialogState createState() => _CustomSearchableDialogState();
}
class _CustomSearchableDialogState extends State<CustomSearchableDialog> {
  late CommonWidgetRefreshProvider provider;

  List<dynamic> localList = [{"id": "", "name": ""}];

  final TextEditingController searchController = TextEditingController();
  bool isExpanded = false;
  final dropdownKey = GlobalKey<DropdownButton2State>();
  void Function(void Function())? functionCallback;
  List<dynamic> selectedItems = [];

  @override
  void initState() {
    super.initState();
    provider = Provider.of<CommonWidgetRefreshProvider>(context, listen: false);
    print("init state expanded $isExpanded");
  }

  @override
  void didUpdateWidget(CustomSearchableDialog oldWidget) {
    super.didUpdateWidget(oldWidget);
    if(oldWidget.selectedItems != selectedItems){
      print("ITs changed item");
    }else{
      print("ITs item is same");
    }
    SchedulerBinding.instance?.addPostFrameCallback((_) {
      provider.refreshWidget();
    });
  }

  @override
  void dispose() {
    searchController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Consumer<CommonWidgetRefreshProvider>(
        builder: (context, value, child) {
      return DropdownButtonHideUnderline(
        child: DropdownButton2(
          key: dropdownKey,
          isExpanded: true,
          dropdownWidth: 250,
          hint: FilterTitleWidget(
              titleText: DATE_STRING,
              icon: isExpanded ? Icons.expand_less : Icons.expand_more),
          icon: Container(),
          items: widget.dropDownValue
              .map((item) => DropdownMenuItem<dynamic>(
                    value: item,
                    enabled: false,
                    child: StatefulBuilder(
                      builder: (BuildContext context,
                          void Function(void Function()) change) {
                        functionCallback = change;
                        final _isSelected = selectedItems.contains(item);
                        return InkWell(
                          onTap: () {
                            _isSelected
                                ? selectedItems.remove(item)
                                : selectedItems.add(item);
                            change(() {});
                          },
                          child: Container(
                            height: double.infinity,
                            padding: const EdgeInsets.symmetric(
                                horizontal: 16.0),
                            color: _isSelected
                                ? SELECTED_BACKGROUND_COLOR
                                : null,
                            child: Row(
                              mainAxisAlignment:
                              MainAxisAlignment.spaceBetween,
                              crossAxisAlignment:
                              CrossAxisAlignment.center,
                              children: [
                                Text(
                                  (item is CustomObj)
                                      ? item.name
                                      : item['name'],
                                  style: TextStyle(
                                      fontSize: widget.listItemFontSize,
                                      color: widget.listItemTextColor,
                                      fontFamily:
                                      widget.listItemFontFamily,
                                      fontWeight:
                                      widget.listItemFontWeight),
                                ),
                                _isSelected
                                    ? Icon(Icons.check,
                                    color: widget.checkMarkColor,
                                    size: widget.checkMarkSize)
                                    : const SizedBox(),
                              ],
                            ),
                          ),
                        );
                      },
                    ),
                  ))
              .toList(),
          onChanged: (dynamic value) {},
          buttonHeight: 40,
          itemHeight: 40,
          alignment: Alignment.center,
          dropdownMaxHeight: 422,
          searchController: searchController,
          dropdownDecoration: BoxDecoration(
            borderRadius: BorderRadius.all(Radius.circular(8)),
            color: Colors.white,
            boxShadow: [
              BoxShadow(
                offset: Offset(0, 0),
                blurRadius: 1,
                spreadRadius: 1,
                color: black14,
              ),
            ],
          ),
          searchInnerWidget: Container(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.start,
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Container(
                  padding: const EdgeInsets.symmetric(horizontal: 16),
                  alignment: Alignment.topCenter,
                  child: TextFormField(
                    controller: searchController,
                    style: TextStyle(
                        fontSize: widget.searchFontSize,
                        fontFamily: widget.searchFontFamily,
                        fontWeight: widget.searchFontWeight,
                        color: widget.searchTextColor),
                    onChanged: (value) async {
                      print("On text changed $value");
                      if (widget.searchCallBack != null) {
                        widget.dropDownValue.clear();
                        widget.searchCallBack!(value);
                      }
                    },
                    decoration: InputDecoration(
                      isDense: true,
                      contentPadding: EdgeInsets.only(left: 8, top: 16),
                      suffixIcon: Icon(Icons.search_rounded),
                      hintText: widget.hintSearch,
                      hintStyle: TextStyle(
                          fontSize: widget.hintSearchFontSize,
                          fontWeight: widget.hintSearchFontWeight,
                          fontFamily: widget.hintSearchFontFamily,
                          color: widget.hintSearchTextColor),
                    ),
                  ),
                ),
                      );}
                    );
                  },
                )
              ],
            ),
          ),
          onMenuStateChange: (isOpen) {
            isExpanded = isOpen;
            if (!isOpen) {
              if (widget.finalValueSelection != null) {
                widget.finalValueSelection!(selectedItems);
              }
            } else {
//              provider.refreshWidget();
   //           dropdownKey.currentState?.setState(() {});
            }
          },
        ),
      );
    });
  }
}
@AhmedLSayed9
Copy link
Owner

I don't know what exactly is the issue here.
Can you provide a simplified working sample that produce the issue please?

@AhmedLSayed9 AhmedLSayed9 added the question Further information is requested label Nov 29, 2022
@radheyshyamjat
Copy link
Author

Trying to make searchable dropdown but dropdown is list but that list is search from api call
Whenever search it will be call api/network call after that we need to update ui based on api call.
Till i hack and working fine but after that i select item's that item whenever i open it will be selected show but whenever i open search and close and again open that selected value will be delected automatically

image

@radheyshyamjat
Copy link
Author

Below simplified version of source code

Main widget which one used for main Page of UI

Container(
              width: 130,
              child: AdvanceDropDownDialog(
                widgetTitle: "Title",
                dropDownValue: listOfValues,  // this listOfValues contain dropdown value
                searchCallBack: (searchKey) {
                  searchRadio(searchKey); // When search happen on actuall in this will call with whatever type in text form field and api call will be happening here after api call success dropdown value need to change and refresh widget
                },
                textEditingController: raTextEditingController,
              ),
            )
class AdvanceDropDownDialog extends StatefulWidget {
  final String widgetTitle;
  final List<dynamic> dropDownValue;
  final Function(String)? searchCallBack;
  final TextEditingController textEditingController;

  const AdvanceDropDownDialog(
      {required this.widgetTitle,
      required this.dropDownValue,
      this.searchCallBack,
      required this.textEditingController,
      Key? key})
      : super(key: key);

  @override
  _AdvanceDropDownDialogState createState() => _AdvanceDropDownDialogState();
}

class _AdvanceDropDownDialogState extends State<AdvanceDropDownDialog> {
  dynamic selectedValue;
  List<dynamic> selectedItems = [];
  bool isExpanded = false;

  @override
  void initState() {
    super.initState();
  }

  @override
  void didUpdateWidget(AdvanceDropDownDialog oldWidget) {
    super.didUpdateWidget(oldWidget);
  }

  @override
  void dispose() {
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      builder: (context, snapshoot) {
        return DropdownButtonHideUnderline(
          child: DropdownButton2(
            isExpanded: true,
            dropdownWidth: 300,
            hint: FilterTitleWidget(
                titleText: widget.widgetTitle,
                icon: isExpanded ? Icons.expand_less : Icons.expand_more),
            icon: Container(),
            items: widget.dropDownValue
                .map((item) => DropdownMenuItem<dynamic>(
                      value: item,
                      enabled: false,
                      child: StatefulBuilder(
                        builder: (BuildContext context,
                            void Function(void Function()) change) {
                          final _isSelected = selectedItems.contains(item);
                          return InkWell(
                            onTap: () {
                              _isSelected
                                  ? selectedItems.remove(item)
                                  : selectedItems.add(item);
                              change(() {});
                            },
                            child: Container(
                              height: double.infinity,
                              padding:
                                  const EdgeInsets.symmetric(horizontal: 16.0),
                              color: _isSelected
                                  ? SELECTED_BACKGROUND_COLOR
                                  : null,
                              child: Row(
                                mainAxisAlignment:
                                    MainAxisAlignment.spaceBetween,
                                crossAxisAlignment: CrossAxisAlignment.center,
                                children: [
                                  Text(
                                    (item is CustomObj)
                                        ? item.name
                                        : item['name'],
                                  ),
                                  _isSelected
                                      ? Icon(Icons.check)
                                      : const SizedBox(),
                                ],
                              ),
                            ),
                          );
                        },
                      ),
                    ))
                .toList(),
            value: selectedValue,
            onChanged: (dynamic value) {},
            buttonHeight: 40,
            itemHeight: 40,
            alignment: Alignment.center,
            dropdownMaxHeight: 200,
            searchController: widget.textEditingController,
            searchInnerWidget: Padding(
              padding: const EdgeInsets.only(
                top: 8,
                // bottom: 4,
                right: 0,
                left: 0,
              ),
              child: TextFormField(
                controller: widget.textEditingController,
                onChanged: (value) {
                  if (widget.searchCallBack != null) {
                    widget.searchCallBack!(value);
                  }
                },
                decoration: InputDecoration(
                  isDense: true,
                  contentPadding: EdgeInsets.only(left: 32, top: 16),
                  suffixIcon: Icon(Icons.search_rounded),
                  hintText: "Search ...",
                ),
              ),
            ),
            searchMatchFn: (item, searchValue) {
              return (item.value
                  .toString()
                  .toLowerCase()
                  .contains(searchValue.toLowerCase()));
            },
            onMenuStateChange: (isOpen) {
              if (!isOpen) {}
              isExpanded = isOpen;
            },
          ),
        );
      },
    );
  }
}

@AhmedLSayed9
Copy link
Owner

Is your issue that the dropdown menu items don't update when you call api?

@radheyshyamjat
Copy link
Author

Yes after api call that not update & selected item i have customized changing background color and right tick mark that also automatically deselect

@AhmedLSayed9
Copy link
Owner

Currently updating an open Dropdown Menu is not fully supported but there's a hacky solution for this as described here #101

@radheyshyamjat
Copy link
Author

Actually this one tried but not working

@AhmedLSayed9
Copy link
Owner

Are you currently able to update the menu when its open?

@radheyshyamjat
Copy link
Author

yes

@radheyshyamjat
Copy link
Author

Main problem is selected items not refresh

@AhmedLSayed9
Copy link
Owner

onTap: () {
    _isSelected
        ? selectedItems.remove(item)
        : selectedItems.add(item);
    change(() {});
    //Add this.. This rebuilds the main StatefulWidget
    setState(() {});
  },

@radheyshyamjat
Copy link
Author

yes actually did this but when two to three time open close dropdown it will be automatically deselect

@radheyshyamjat
Copy link
Author

** Please keep in mind i am using like reusable widget like multiple instance

@AhmedLSayed9
Copy link
Owner

yes actually did this but when two to three time open close dropdown it will be automatically deselect

I'm not sure why this is happening so I need to test your code.
Can you update your code so I can actually run it? Currently your sample has different dependencies which I don't have.

@radheyshyamjat
Copy link
Author

latest one shared that have no different dependencies
If possible we can connect also

@AhmedLSayed9
Copy link
Owner

Actually it has dependencies.
I can't run it on a new flutter project.

@AhmedLSayed9
Copy link
Owner

Open new flutter project and try to let your sample code work on it so I can test it too.
For the api call, you can simulate it by await Future.delayed

@radheyshyamjat
Copy link
Author

Okay sending you with open create new flutter project and adding all on that

@radheyshyamjat
Copy link
Author

radheyshyamjat commented Nov 29, 2022

Full example added please check

class AdvanceDialog extends StatefulWidget {
  const AdvanceDialog({Key? key}) : super(key: key);

  @override
  _AdvanceDialogState createState() => _AdvanceDialogState();
}

class _AdvanceDialogState extends State<AdvanceDialog> {
  final TextEditingController searchController = TextEditingController();
  final TextEditingController searchController2 = TextEditingController();
  List<dynamic> dropdownValue = [
    "Aetna",
    "Allegiance",
    "Anthem",
    "BlueCross",
    "BlueCrossandBlueShield",
    "BlueShield",
    "Children'sProgram",
    "CHIP",
    "Cigna",
    "FirstChoiceHealth",
    "Humana",
    "Kaiser(Out-of-Network)",
    "Magellan",
    "Medicaid",
    "Medicare",
    "MHNetBehavioralHealth",
    "Optum",
    "OtherInsuranceEBMS",
    "OutofNetwork",
    "PacificSource",
    "TRICARE",
    "TriWest",
    "UMR",
    "UnitedHealthcare"
  ];

  List<dynamic> searchItems = [];
  List<dynamic> searchItems2 = [];
  List<dynamic> dropdownValue2 = [
    "Aetna",
    "Allegiance",
    "Anthem",
    "BlueCross",
    "BlueCrossandBlueShield",
    "BlueShield",
    "Children'sProgram",
    "CHIP",
    "Cigna",
    "FirstChoiceHealth",
    "Humana",
    "Kaiser(Out-of-Network)",
    "Magellan",
    "Medicaid",
    "Medicare",
    "MHNetBehavioralHealth",
    "Optum",
    "OtherInsuranceEBMS",
    "OutofNetwork",
    "PacificSource",
    "TRICARE",
    "TriWest",
    "UMR",
    "UnitedHealthcare"
  ];

  @override
  void initState() {
    super.initState();
    searchItems = dropdownValue;
    searchItems2 = dropdownValue2;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          AdvanceDropDownDialog(
              dropDownValue: searchItems,
              textEditingController: searchController,
              searchCallBack: (p0) {
                getValue(p0);
              }),
          AdvanceDropDownDialog(
              dropDownValue: searchItems2,
              textEditingController: searchController2,
              searchCallBack: (p0) {
                getValue2(p0);
              })
        ],
      ),
    );
  }

  getValue(String searchKey) async {
    print("Search key $searchKey");
    if (searchKey.isNotEmpty) {
      await Future.delayed(const Duration(seconds: 1));
      searchItems.clear();
      dropdownValue.forEach((element) {
        if (element.toString().contains(searchKey)) {
          searchItems.add(element);
        }
      });
    } else {
      searchItems = dropdownValue;
    }
    setState(() {});
  }

  getValue2(String searchKey) async {
    print("Search key $searchKey");
    if (searchKey.isNotEmpty) {
      searchItems2.clear();
      dropdownValue2.forEach((element) {
        if (element.toString().contains(searchKey)) {
          searchItems2.add(element);
        }
      });
    } else {
      searchItems2 = dropdownValue2;
    }
    setState(() {});
  }

  @override
  void dispose() {
    searchController.dispose();
    searchController2.dispose();
    super.dispose();
  }
}

class AdvanceDropDownDialog extends StatefulWidget {
  final List<dynamic> dropDownValue;
  final Function(String)? searchCallBack;
  final TextEditingController textEditingController;

  const AdvanceDropDownDialog(
      {required this.dropDownValue,
      this.searchCallBack,
      required this.textEditingController,
      Key? key})
      : super(key: key);

  @override
  _AdvanceDropDownDialogState createState() => _AdvanceDropDownDialogState();
}

class _AdvanceDropDownDialogState extends State<AdvanceDropDownDialog> {
  dynamic selectedValue;
  List<dynamic> selectedItems = [];
  bool isExpanded = false;

  @override
  void initState() {
    super.initState();
  }

  @override
  void didUpdateWidget(AdvanceDropDownDialog oldWidget) {
    super.didUpdateWidget(oldWidget);
  }

  @override
  void dispose() {
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      builder: (context, snapshoot) {
        return DropdownButtonHideUnderline(
          child: DropdownButton2(
            isExpanded: true,
            dropdownWidth: 300,
            hint: Container(
              height: 17,
              margin: const EdgeInsets.symmetric(horizontal: 5),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.center,
                crossAxisAlignment: CrossAxisAlignment.center,
                children: const [
                  Text("Title Text"),
                  SizedBox(width: 5),
                  Icon(Icons.expand_less),
                ],
              ),
            ),
            icon: Container(),
            items: widget.dropDownValue
                .map((item) => DropdownMenuItem<dynamic>(
                      value: item,
                      enabled: false,
                      child: StatefulBuilder(
                        builder: (BuildContext context,
                            void Function(void Function()) change) {
                          final _isSelected = selectedItems.contains(item);
                          return InkWell(
                            onTap: () {
                              _isSelected
                                  ? selectedItems.remove(item)
                                  : selectedItems.add(item);
                              change(() {});
                            },
                            child: Container(
                              height: double.infinity,
                              padding:
                                  const EdgeInsets.symmetric(horizontal: 16.0),
                              color: _isSelected
                                  ? Colors.tealAccent.shade400
                                  : null,
                              child: Row(
                                mainAxisAlignment:
                                    MainAxisAlignment.spaceBetween,
                                crossAxisAlignment: CrossAxisAlignment.center,
                                children: [
                                  Text(item),
                                  _isSelected
                                      ? const Icon(Icons.check)
                                      : const SizedBox(),
                                ],
                              ),
                            ),
                          );
                        },
                      ),
                    ))
                .toList(),
            value: selectedValue,
            onChanged: (dynamic value) {},
            buttonHeight: 40,
            itemHeight: 40,
            alignment: Alignment.center,
            dropdownMaxHeight: 200,
            searchController: widget.textEditingController,
            searchInnerWidget: Padding(
              padding: const EdgeInsets.only(
                top: 8,
                // bottom: 4,
                right: 0,
                left: 0,
              ),
              child: TextFormField(
                controller: widget.textEditingController,
                onChanged: (value) {
                  if (widget.searchCallBack != null) {
                    widget.searchCallBack!(value);
                  }
                },
                decoration: const InputDecoration(
                  isDense: true,
                  contentPadding: EdgeInsets.only(left: 32, top: 16),
                  suffixIcon: Icon(Icons.search_rounded),
                  hintText: "Search ...",
                ),
              ),
            ),
            onMenuStateChange: (isOpen) {
              if (!isOpen) {}
              isExpanded = isOpen;
            },
          ),
        );
      },
    );
  }
}

@AhmedLSayed9
Copy link
Owner

This can be tested, Thanks!
What are the steps to produce the issue?

@AhmedLSayed9
Copy link
Owner

Here you're doing searchItems.clear() and as searchItems is the same reference as dropdownValue, the forEach will never execute.

await Future.delayed(const Duration(seconds: 1));
      searchItems.clear();
      //This forEach never execute
      dropdownValue.forEach((element) {
        if (element.toString().toLowerCase().contains(searchKey)) {
          searchItems.add(element);
        }
      });

You need to clone the list in initState instead of copying the object reference:

  @override
  void initState() {
    super.initState();
    searchItems = [...dropdownValue];
  }

Another thing is as you're using the default SearchMatchFn you need to add toLowerCase() to your condition:

  dropdownValue.forEach((element) {
    if (element.toString().toLowerCase().contains(searchKey.toLowerCase())) {
      searchItems.add(element);
    }
  });

@AhmedLSayed9
Copy link
Owner

As this is unrelated to the package, I'll close it.
Feel free to ask for re-open if you disagree.

@AhmedLSayed9 AhmedLSayed9 added invalid This doesn't seem right and removed question Further information is requested labels Nov 29, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
invalid This doesn't seem right
Projects
None yet
Development

No branches or pull requests

2 participants