flutterdartsetstateflutter-showmodalbottomsheet

Flutter Filter Chip Issues with Bottom Sheet Modal


I have a bottom list modal in flutter that I want to use as a way to add and remove tags. I am using filter chips as the way to toggle from a pre-built list of tags.

I have the bottom sheet modal working great, pops up on icon button click. The filter chips show in the modal but nothing happens when I tap them... But if I close the bottom sheet modal then re-open it they they look selected...

         IconButton(
              icon: const Icon(Icons.add_circle),
              color: const Color(0xFF20647A),
              //show tags sheeet
              onPressed: () {
                showModalBottomSheet<void>(
                  context: context,
                  builder: (BuildContext context) {
                    return SizedBox(
                      height: MediaQuery.of(context).size.height * 0.58,
                      width: MediaQuery.of(context).size.width,
                      child:
                          //bottom sheet body
                          Column(
                        mainAxisAlignment: MainAxisAlignment.start,
                        mainAxisSize: MainAxisSize.min,
                        children: <Widget>[
                          ElevatedButton(
                            child: const Text('Close BottomSheet'),
                            onPressed: () => Navigator.pop(context),
                          ),
                          const SizedBox(height: 16),
                          Wrap(
                            spacing: 10,
                            children: tagListFull.map(
                              (category) {
                                return FilterChip(
                                  selected:
                                      selectedTags.contains(category),
                                  onSelected: (bool selected) {
                                    if (selected) {
                                      selectedTags.add(category);
                                    } else {
                                      selectedTags.remove(category);
                                    }
                                  },
                                  label: Text(category),
                                );
                              },
                            ).toList(),
                          ),
                        ],
                      ),
                    );
                  },
                );
              },
            ),

My initial gut was that it was a setState issue, but when I added set state to the add(category) and remove(category) then taping them didnt work at all even when closing and re-openning the bottom sheet modal.


Solution

  • The modal bottom sheet itself is not stateful, the widget that shows the modal bottom sheet is. Therefore, when you call setState, the modal bottom sheet knows nothing about what just happened.

    The reason that closing the modal bottom sheet and re-opening then shows the updated state is because when the modal bottom sheet is re-opened (i.e. built), it simply accesses the current state, which was modified since the last time the modal bottom sheet was built through a call to showModalBottomSheet.

    You can solve this in numerous ways. I'll show you an approach that allows the modal bottom sheet filter chips to change their state.

    You can implement a stateful widget that rebuilds the FilterChip on tap:

    class MyFilterChip extends StatefulWidget {
      const MyFilterChip({super.key, required this.onChanged});
    
      final void Function(bool) onChanged;
    
      @override
      State<MyFilterChip> createState() => MyFilterChipState();
    }
    
    class MyFilterChipState extends State<MyFilterChip> {
      bool _selected = false;
      bool get selected => _selected;
    
      @override
      Widget build(BuildContext context) {
        return FilterChip(
          label: const Text("filter 1"),
          selected: _selected,
          onSelected: (value) {
            setState(() => _selected = value);
            widget.onChanged(value);
          },
        );
      }
    }
    

    You can use the onChanged callback from to update your selectedTags. There are other approaches such as notifiers, but this one is simple.

    You would simply replace your FilterChip widgets with MyFilterChip widgets:

    showModalBottomSheet<void>(
      context: context,
      builder: (context) {
        return SizedBox(
          height: 200.0,
            child: MyFilterChip(
              onChanged: (isSelected) {
                if (selected) {
                  selectedTags.add(category);
                } else {
                  selectedTags.remove(category);
                }
              },
            ),
          );
        },
      );