Search code examples
flutterdartdrop-down-menuflutter-layout

How do I ensure that elements inside the dropdown menu can only be selected once in Flutter?


class CardWidget extends StatefulWidget {
  final String title;
  final Color color;
  final IconData icon;
  const CardWidget({
    super.key,
    required this.title,
    required this.color,
    required this.icon,
  });

  @override
  State<CardWidget> createState() => _CardWidgetState();
}

class _CardWidgetState extends State<CardWidget> {    

  final List<String> items = [
        'Key 1',
        'Key 2',
        'Key 3',
        'Key 4',
      ];
      String? selectedValue;
 @override
  Widget build(BuildContext context) {
         return SizedBox(
          height: 200,
          width: 200,
          child: Padding(
            padding: const EdgeInsets.all(8.0),
            child: Card(
              elevation: 10,
              shape: RoundedRectangleBorder(
                borderRadius: BorderRadius.circular(20.0),
              ),
              color: widget.color,
              child: Column(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: [
                  Icon(
                    widget.icon,
                    color: Colors.white,
                    size: 32,
                  ),
                  Center(
                    child: Text(
                      widget.title,
                      style: GoogleFonts.poppins(
                          color: Colors.white,
                          fontSize: 14,
                          fontWeight: FontWeight.bold),
                    ),
                  ),
                  DropdownButtonHideUnderline(
                    child: DropdownButton2(
                      isExpanded: true,
                      hint: Row(
                        children: [
                          Expanded(
                            child: Center(
                              child: Text(
                                'Select a Key',
                                style: dropdownTitle,
                                overflow: TextOverflow.ellipsis,
                              ),
                            ),
                          ),
                        ],
                      ),
                      items: items
                          .map((item) => DropdownMenuItem<String>(
                                value: item,
                                child: Center(
                                  child: Text(
                                    item,
                                    style: dropdownItems,
                                    overflow: TextOverflow.ellipsis,
                                  ),
                                ),
                              ))
                          .toList(),
                      value: selectedValue,
                      onChanged: (value) {
                        setState(() {
                          selectedValue = value as String;
                        });
                      },
                      icon: const Icon(
                        Icons.arrow_forward_ios_outlined,
                      ),
                      iconSize: 20,
                      iconEnabledColor: Color.fromARGB(255, 31, 10, 88),
                      iconDisabledColor: Colors.grey,
                      buttonHeight: 40,
                      buttonWidth: 135,
                      buttonPadding: const EdgeInsets.only(
                        left: 15,
                        right: 15,
                      ),
                      buttonDecoration: BoxDecoration(
                        borderRadius: BorderRadius.circular(14),
                        border: Border.all(
                          color: Colors.black26,
                        ),
                        color: Color.fromARGB(255, 255, 255, 255),
                      ),
                      buttonElevation: 2,
                      itemHeight: 40,
                      itemPadding: const EdgeInsets.only(left: 14, right: 14),
                      dropdownMaxHeight: 200,
                      dropdownWidth: 200,
                      dropdownPadding: null,
                      dropdownDecoration: BoxDecoration(
                        borderRadius: BorderRadius.circular(10),
                        color: Colors.white,
                      ),
                      dropdownElevation: 8,
                      scrollbarRadius: const Radius.circular(10),
                      scrollbarThickness: 6,
                      scrollbarAlwaysShow: true,
                      offset: const Offset(-20, 0),
                    ),
                  ),

CardWidget I created.

return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: const [
              CardWidget(
                      title: 'Mute',
                      color: Color.fromARGB(255, 211, 38, 25),
                      icon: Icons.volume_off_outlined,
                    ),
                CardWidget(
                      title: 'Mute',
                      color: Color.fromARGB(255, 211, 38, 25),
                      icon: Icons.volume_off_outlined,
                    ),
              CardWidget(
                      title: 'Mute',
                      color: Color.fromARGB(255, 211, 38, 25),
                      icon: Icons.volume_off_outlined,
                    ),
               CardWidget(
                      title: 'Mute',
                      color: Color.fromARGB(255, 211, 38, 25),
                      icon: Icons.volume_off_outlined,
                    ),
              ],
            ),

The homepage where I use CardWidget.

enter image description here

I can call my list and select the elements inside the list.

enter image description here

However, as seen in the image, it can be active even though 'key 1' is selected in all dropdowns. What I'm dealing with is for example 'key 1' is selected in the first dropdown, if 'key 1' is selected in the second dropdown it will just show it there. The second dropdown should not write 'key 1' under the menu, it should write in the last selected one. Leave the first one blank.


Solution

  • Removed selected Item from others dropdown selection: I am using Valunotifer to simplify the snippet and the logic is

    onChanged: (value) {
        final index =
            cardsNotifier.value.indexWhere((element) => element.id == id);
    
        for (int i = 0; i < cardsNotifier.value.length; i++) {
          if (index != i && cardsNotifier.value[i].selectedItem == value) {
            cardsNotifier.value[i].selectedItem = null;
          }
        }
        cardsNotifier.value[index].selectedItem = value;
    
        cardsNotifier.value = cardsNotifier.value.toList();
      },
    

    Test widget

    class FA extends StatefulWidget {
      const FA({super.key});
    
      @override
      State<FA> createState() => _FAState();
    }
    
    class CardHelper {
      final int id;
      String? selectedItem;
      CardHelper({
        required this.id,
        this.selectedItem,
      });
    }
    
    ValueNotifier<List<CardHelper>> cardsNotifier = ValueNotifier(List.generate(
        4,
        (index) => CardHelper(
              id: index,
            )));
    
    class _FAState extends State<FA> {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: Row(children: [
            for (int i = 0; i < cardsNotifier.value.length; i++)
              CardWidget(
                title: 'Mute',
                id: cardsNotifier.value[i].id,
              ),
          ]),
        );
      }
    }
    
    class CardWidget extends StatelessWidget {
      final String title;
    
      final int id;
    
      const CardWidget({
        super.key,
        required this.id,
        required this.title,
      });
    
      @override
      Widget build(BuildContext context) {
        return SizedBox(
          height: 200,
          width: 200,
          child: Padding(
            padding: const EdgeInsets.all(8.0),
            child: Card(
              elevation: 10,
              shape: RoundedRectangleBorder(
                borderRadius: BorderRadius.circular(20.0),
              ),
              child: Column(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: [
                  itemBuilder(),
                ],
              ),
            ),
          ),
        );
      }
    
      ValueListenableBuilder<List<CardHelper>> itemBuilder() {
        return ValueListenableBuilder<List<CardHelper>>(
          valueListenable: cardsNotifier,
          builder: (context, data, child) => DropdownButtonHideUnderline(
            child: DropdownButton2(
              isExpanded: true,
              hint: Row(
                children: [
                  Expanded(
                    child: Center(
                      child: Text(
                        'Select a Key',
                        overflow: TextOverflow.ellipsis,
                      ),
                    ),
                  ),
                ],
              ),
              items: ['Key 1', 'Key 2', 'Key 3', 'Key 4']
                  .map((item) => DropdownMenuItem<String>(
                        value: item,
                        child: Center(
                          child: Text(
                            item,
                            overflow: TextOverflow.ellipsis,
                          ),
                        ),
                      ))
                  .toList(),
              value: data[id].selectedItem,
              onChanged: (value) {
                final index =
                    cardsNotifier.value.indexWhere((element) => element.id == id);
    
                for (int i = 0; i < cardsNotifier.value.length; i++) {
                  if (index != i && cardsNotifier.value[i].selectedItem == value) {
                    cardsNotifier.value[i].selectedItem = null;
                  }
                }
                cardsNotifier.value[index].selectedItem = value;
    
                cardsNotifier.value = cardsNotifier.value.toList();
              },
              icon: const Icon(
                Icons.arrow_forward_ios_outlined,
              ),
              iconSize: 20,
            ),
          ),
        );
      }
    }
    

    Remove selected item from dropdown

    I am using a helper class for simplification

    class CardHelper {
      final int id;
      final List<String> items;
      final String? selectedItem;
    
      CardHelper({
        required this.id,
        required this.items,
        this.selectedItem,
      });
    }
    

    And the CardWidget widget take data as input and provide a callback with selected item.

    class CardWidget extends StatefulWidget {
      final List<String> items;
      final Function(String?) selectedItemCallback;
       .....
      const CardWidget({
        super.key,
        required this.items,
    

    And onChanged will be

    items: widget.items
        .map((item) => DropdownMenuItem<String>(
              value: item,
              child: Center(
                child: Text(
                  item,
                  overflow: TextOverflow.ellipsis,
                ),
              ),
            ))
        .toList(),
    value: selectedValue,
    onChanged: (value) {
      widget.selectedItemCallback(value);
    

    Now for the parent widget, we will create a list for cards

      final cards = List.generate(
          4,
          (index) =>
              CardHelper(id: index, items: ['Key 1', 'Key 2', 'Key 3', 'Key 4']));
    

    And rest logic lies

    for (int i = 0; i < cards.length; i++)
        CardWidget(
          title: 'Mute',
          color: Color.fromARGB(255, 211, 38, 25),
          icon: Icons.volume_off_outlined,
          items: cards[i].items,
          selectedItemCallback: (p0) {
            for (int j = 0; j < cards.length; j++) {
              if (i == j) continue;
              cards[j].items.remove(p0);
            }
            setState(() {});
          },
        ),
    

    Play widget

    
    void main(List<String> args) {
      runApp(MaterialApp(home: FA()));
    }
    
    class FA extends StatefulWidget {
      const FA({super.key});
    
      @override
      State<FA> createState() => _FAState();
    }
    
    class CardHelper {
      final int id;
      final List<String> items;
      final String? selectedItem;
    
      CardHelper({
        required this.id,
        required this.items,
        this.selectedItem,
      });
    }
    
    class _FAState extends State<FA> {
      final cards = List.generate(
          4,
          (index) =>
              CardHelper(id: index, items: ['Key 1', 'Key 2', 'Key 3', 'Key 4']));
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: Row(
            children: [
              for (int i = 0; i < cards.length; i++)
                CardWidget(
                  title: 'Mute',
                  color: Color.fromARGB(255, 211, 38, 25),
                  icon: Icons.volume_off_outlined,
                  items: cards[i].items,
                  selectedItemCallback: (p0) {
                    for (int j = 0; j < cards.length; j++) {
                      if (i == j) continue;
                      cards[j].items.remove(p0);
                    }
                    setState(() {});
                  },
                ),
            ],
          ),
        );
      }
    }
    
    class CardWidget extends StatefulWidget {
      final List<String> items;
      final String title;
      final Color color;
      final IconData icon;
      final Function(String?) selectedItemCallback;
      const CardWidget({
        super.key,
        required this.items,
        required this.title,
        required this.color,
        required this.icon,
        required this.selectedItemCallback,
      });
    
      @override
      State<CardWidget> createState() => _CardWidgetState();
    }
    
    class _CardWidgetState extends State<CardWidget> {
      String? selectedValue;
      @override
      Widget build(BuildContext context) {
        return SizedBox(
          height: 200,
          width: 200,
          child: Padding(
            padding: const EdgeInsets.all(8.0),
            child: Card(
              elevation: 10,
              shape: RoundedRectangleBorder(
                borderRadius: BorderRadius.circular(20.0),
              ),
              color: widget.color,
              child: Column(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: [
                  Icon(
                    widget.icon,
                    color: Colors.white,
                    size: 32,
                  ),
                  Center(
                    child: Text(
                      widget.title,
                    ),
                  ),
                  DropdownButtonHideUnderline(
                    child: DropdownButton2(
                      isExpanded: true,
                      hint: Row(
                        children: [
                          Expanded(
                            child: Center(
                              child: Text(
                                'Select a Key',
                                overflow: TextOverflow.ellipsis,
                              ),
                            ),
                          ),
                        ],
                      ),
                      items: widget.items
                          .map((item) => DropdownMenuItem<String>(
                                value: item,
                                child: Center(
                                  child: Text(
                                    item,
                                    overflow: TextOverflow.ellipsis,
                                  ),
                                ),
                              ))
                          .toList(),
                      value: selectedValue,
                      onChanged: (value) {
                        widget.selectedItemCallback(value);
                        setState(() {
                          selectedValue = value as String;
                        });
                      },
                      icon: const Icon(
                        Icons.arrow_forward_ios_outlined,
                      ),
                      iconSize: 20,
                      iconEnabledColor: Color.fromARGB(255, 31, 10, 88),
                      iconDisabledColor: Colors.grey,
                      buttonHeight: 40,
                      buttonWidth: 135,
                      buttonPadding: const EdgeInsets.only(
                        left: 15,
                        right: 15,
                      ),
                      buttonDecoration: BoxDecoration(
                        borderRadius: BorderRadius.circular(14),
                        border: Border.all(
                          color: Colors.black26,
                        ),
                        color: Color.fromARGB(255, 255, 255, 255),
                      ),
                      buttonElevation: 2,
                      itemHeight: 40,
                      itemPadding: const EdgeInsets.only(left: 14, right: 14),
                      dropdownMaxHeight: 200,
                      dropdownWidth: 200,
                      dropdownPadding: null,
                      dropdownDecoration: BoxDecoration(
                        borderRadius: BorderRadius.circular(10),
                        color: Colors.white,
                      ),
                      dropdownElevation: 8,
                      scrollbarRadius: const Radius.circular(10),
                      scrollbarThickness: 6,
                      scrollbarAlwaysShow: true,
                      offset: const Offset(-20, 0),
                    ),
                  ),
                ],
              ),
            ),
          ),
        );
      }
    }