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.
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);
}
},
),
);
},
);