Is there an equivalent widget in flutter to the "select multiple" element in HTML - select

I am searching for a widget in flutter that is equal to
<select multiple=""></select>
in flutter.
An example implementation (for the web) is MaterializeCSS Select Multiple
As seen above I should be able to provide a list of items (with some of them preselected) and at the end retrieve a list of selected items or a map or something else.
An example implementation or a link to a documentation is very appreciated.

I don't think that a widget like that currently exists in Flutter, but you can build one yourself.
On mobile phones with limited screen space it would probably make sense to display a dialog with a submit button, like this native Android dialog.
Here is a rough sketch how to implement such a dialog in less than 100 lines of code:
class MultiSelectDialogItem<V> {
const MultiSelectDialogItem(this.value, this.label);
final V value;
final String label;
}
class MultiSelectDialog<V> extends StatefulWidget {
MultiSelectDialog({Key key, this.items, this.initialSelectedValues}) : super(key: key);
final List<MultiSelectDialogItem<V>> items;
final Set<V> initialSelectedValues;
#override
State<StatefulWidget> createState() => _MultiSelectDialogState<V>();
}
class _MultiSelectDialogState<V> extends State<MultiSelectDialog<V>> {
final _selectedValues = Set<V>();
void initState() {
super.initState();
if (widget.initialSelectedValues != null) {
_selectedValues.addAll(widget.initialSelectedValues);
}
}
void _onItemCheckedChange(V itemValue, bool checked) {
setState(() {
if (checked) {
_selectedValues.add(itemValue);
} else {
_selectedValues.remove(itemValue);
}
});
}
void _onCancelTap() {
Navigator.pop(context);
}
void _onSubmitTap() {
Navigator.pop(context, _selectedValues);
}
#override
Widget build(BuildContext context) {
return AlertDialog(
title: Text('Select animals'),
contentPadding: EdgeInsets.only(top: 12.0),
content: SingleChildScrollView(
child: ListTileTheme(
contentPadding: EdgeInsets.fromLTRB(14.0, 0.0, 24.0, 0.0),
child: ListBody(
children: widget.items.map(_buildItem).toList(),
),
),
),
actions: <Widget>[
FlatButton(
child: Text('CANCEL'),
onPressed: _onCancelTap,
),
FlatButton(
child: Text('OK'),
onPressed: _onSubmitTap,
)
],
);
}
Widget _buildItem(MultiSelectDialogItem<V> item) {
final checked = _selectedValues.contains(item.value);
return CheckboxListTile(
value: checked,
title: Text(item.label),
controlAffinity: ListTileControlAffinity.leading,
onChanged: (checked) => _onItemCheckedChange(item.value, checked),
);
}
}
You can use it like this:
void _showMultiSelect(BuildContext context) async {
final items = <MultiSelectDialogItem<int>>[
MultiSelectDialogItem(1, 'Dog'),
MultiSelectDialogItem(2, 'Cat'),
MultiSelectDialogItem(3, 'Mouse'),
];
final selectedValues = await showDialog<Set<int>>(
context: context,
builder: (BuildContext context) {
return MultiSelectDialog(
items: items,
initialSelectedValues: [1, 3].toSet(),
);
},
);
print(selectedValues);
}

Is this what you want?
In case you need a short and ready to use code, follow this article
import 'package:flutter/material.dart';
import 'package:multiple_selection_dialogue_app/widgets/multi_select_dialog.dart';
/// A demo page that displays an [ElevatedButton]
class DemoPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
/// Stores the selected flavours
List<String> flavours = [];
return ElevatedButton(
child: Text('Flavours'),
onPressed: () async {
flavours = await showDialog<List<String>>(
context: context,
builder: (_) => MultiSelectDialog(
question: Text('Select Your Flavours'),
answers: [
'Chocolate',
'Caramel',
'Vanilla',
'Peanut Butter'
])) ??
[];
print(flavours);
// Logic to save selected flavours in the database
});
}
}
import 'package:flutter/material.dart';
/// A Custom Dialog that displays a single question & list of answers.
class MultiSelectDialog extends StatelessWidget {
/// List to display the answer.
final List<String> answers;
/// Widget to display the question.
final Widget question;
/// List to hold the selected answer
/// i.e. ['a'] or ['a','b'] or ['a','b','c'] etc.
final List<String> selectedItems = [];
/// Map that holds selected option with a boolean value
/// i.e. { 'a' : false}.
static Map<String, bool> mappedItem;
MultiSelectDialog({this.answers, this.question});
/// Function that converts the list answer to a map.
Map<String, bool> initMap() {
return mappedItem = Map.fromIterable(answers,
key: (k) => k.toString(),
value: (v) {
if (v != true && v != false)
return false;
else
return v as bool;
});
}
#override
Widget build(BuildContext context) {
if (mappedItem == null) {
initMap();
}
return SimpleDialog(
title: question,
children: [
...mappedItem.keys.map((String key) {
return StatefulBuilder(
builder: (_, StateSetter setState) => CheckboxListTile(
title: Text(key), // Displays the option
value: mappedItem[key], // Displays checked or unchecked value
controlAffinity: ListTileControlAffinity.platform,
onChanged: (value) => setState(() => mappedItem[key] = value)),
);
}).toList(),
Align(
alignment: Alignment.center,
child: ElevatedButton(
style: ButtonStyle(visualDensity: VisualDensity.comfortable),
child: Text('Submit'),
onPressed: () {
// Clear the list
selectedItems.clear();
// Traverse each map entry
mappedItem.forEach((key, value) {
if (value == true) {
selectedItems.add(key);
}
});
// Close the Dialog & return selectedItems
Navigator.pop(context, selectedItems);
}))
],
);
}
}
import 'package:flutter/material.dart';
import 'package:multiple_selection_dialogue_app/pages/demo_page.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: DemoPage(),
),
),
);
}
}

Related

Infinite-scroll listview.builder - to expand or not to expand... and more provider value not updating and how to fix "RenderFlex overflowed"

I am trying to build a view/route that will list items fetched from a REST source.
I want to show a notification item below the list while the data is being fetched.
But my ListView builder is constructed around the fetched data's structure, so I figured just have a ListTile fit some appropriate UX elements below the generated list inside a Column - which was kinda working great - or so I thought - until the list grows to fill the screen causing RenderFlex overflowed error. Wrapping the ListView builder in Expanded fixed that but moved the indicator to the bottom of the screen.
In trying to fix it I seem to have broken more of the plumbing and the boolean variable that should control the idicator widget; isLoading: stockSet.isBusyLoading doesn't seem to update.
At the moment if I hardcode it as `` it does sit in the appropraite position but I am back with the RenderFlex overflow.
Once all of this is working I'll be wanting to automatically load items untill the screen is full - not sure where I'll be triggering that from yet.
class MyStockSet extends StatefulWidget {
const MyStockSet({super.key});
static const indexStr = 'stocks';
static const labelStr = 'Properties';
#override
State<MyStockSet> createState() => _MyStockSetState();
}
class _MyStockSetState extends State<MyStockSet> {
#override
Widget build(BuildContext context) {
const String imagePath = 'assets/images/${MyStockSet.indexStr}.png';
var assetImage = const AssetImage(imagePath);
//var stockSet = context.watch<StockSet>(); <- didn't work either
var stockSet = Provider.of<StockSet>(context,listen: false);
return Scaffold(
appBar: AppBar(
title: Row(
children: [
AscHero(
assetImage: assetImage,
tag: MyStockSet.indexStr,
title: MyStockSet.labelStr,
radius: 32,
),
const SizedBox(width: 12),
const Text(MyStockSet.labelStr),
],
),
actions: [
IconButton(
onPressed: () {
var stockSet = context.read<StockSet>();
int newNr = stockSet.stocks.length + 1;
Stock tmpstock = Stock(
id: newNr,
title: 'test$newNr',
thumbUrl: 'url',
description: 'desc');
stockSet.add(tmpstock);
},
icon: const Icon(Icons.add),
),
IconButton(
onPressed: () {
developer.log('btn before isBusyLoading ${stockSet.isBusyLoading}');
stockSet.fetch();
developer.log('after btn isBusyLoading ${stockSet.isBusyLoading}');
},
icon: const Icon(Icons.handshake),
),
],
),
body: Column(
children: [
Row(
// these will be filters, order toggle etc.
children: [
ElevatedButton(
onPressed: () => developer.log('Btn pressed.'),
child: Text('Btn')),
],
),
Expanded(
child: Column(children: [
_StockListView(),
LoadingStockListItemNotif(
isLoading: true,
),
]),
),
],
),
);
}
}
class _StockListView extends StatefulWidget {
#override
State<_StockListView> createState() => _StockListViewState();
}
class _StockListViewState extends State<_StockListView> {
#override
void didChangeDependencies() {
super.didChangeDependencies();
developer.log('_StockListView didChangeDependencies()');
// developer.log('scroll pos ${_scrollController.position}');
}
#override
Widget build(BuildContext context) {
var stockSet = context.watch<StockSet>();
return ListView.builder(
// controller: _scrollController,
shrinkWrap: true,
itemCount: stockSet.stocks.length,
itemBuilder: (context, index) => InkWell(
child: StockListItem(
stock: stockSet.stocks[index],
),
onTap: () => Navigator.pushNamed(
context,
'/stocks/stock',
arguments: ScreenArguments(stockSet.stocks[index]),
),
),
);
}
void _scrollListener() {
developer.log('_scrollListener');
}
}
and
class StockSet extends ChangeNotifier {
final List<Stock> _stocks = [];
late bool isBusyLoading = false;
List<Stock> get stocks => _stocks;
void add(Stock stock) {
_stocks.add(stock);
developer.log('added stock :${stock.title}');
notifyListeners();
}
void remove(Stock stock) {
_stocks.remove(stock);
notifyListeners();
}
Future<void> fetch() async {
developer.log('fetch() iL T');
isBusyLoading = true;
notifyListeners();
Stock tmpStock = await _fetchAStock();
developer.log('fetch() iL F');
isBusyLoading = false;
notifyListeners();
add(tmpStock);
}
Future<Stock> _fetchAStock() async {
developer.log('fetch stock ');
final response = await http.get(
Uri.https(
//...
),
);
developer.log('response.statusCode:${response.statusCode}');
if (response.statusCode == 200) {
final Map<String, dynamic> map = json.decode(response.body);
return Stock(
id: map['id'] as int,
title: map['title'] as String,
description: map['description'] as String,
thumbUrl: map['thumbUrl'] as String,
);
}
throw Exception('error fetching stocks:');
}
}
Apologies for the convoluted question.
Add mainAxisSize : MainAxisSize.min for the column inside the expanded widget. The expanded doesn't have any bounds and that's why it throws an error. You can wrap the column with a SingleChildScrollView if you have long content to display
This worked for me!
Just set the shrinkWrap attribute to true
Main lesson:
Don't fight the framework.
Answer:
Instead of tying yourself into Möbius knots trying to put the ListView's functionality outside of itself; use the fact that the ListView.builder allows you to sculpt the logic of how it gets built and what it will contain - given that the provider can trigger its rebuild when the variable in the data set changes.
In other words; by increasing the loop of the builder, you can insert a kind of footer to the Listview. The appearance (or not) of that can depend on the provider, provided it fires the appropriate notifyListeners()s etc.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:equatable/equatable.dart';
import 'dart:async';
class ItemSetRoute extends StatefulWidget {
const ItemSetRoute({Key? key}) : super(key: key);
#override
State<ItemSetRoute> createState() => _ItemSetRouteState();
}
class _ItemSetRouteState extends State<ItemSetRoute> {
#override
Widget build(BuildContext context) {
var itemSet = Provider.of<ItemSet>(
context,
listen: true /* in order to rebuild */,
);
return Scaffold(
appBar: AppBar(title: const Text('Test'), actions: [
IconButton(
onPressed: () {
itemSet.fetch();
},
icon: const Icon(Icons.download),
)
]),
body: Column(
//screen
children: [
Row(
children: [
ElevatedButton(
onPressed: () {
itemSet.fetch();
},
child: const Text('Btn')),
],
),
Expanded(
child: ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemCount: itemSet.items.length + 1,
itemBuilder: (context, index) {
/* logic here to create a kind of footer of the ListView */
if (index <= itemSet.items.length - 1) {
return InkWell(
child: ItemListItem(
item: itemSet.items[index],
),
onTap: () {
//('Item tapped, navigate etc.');
});
} else {
return LoadingItemNotifier(
isLoading: itemSet.isBusyLoading,
);
}
},
),
),
],
),
);
}
}
//Models
class ItemListItem extends StatelessWidget {
const ItemListItem({Key? key, required this.item}) : super(key: key);
final Item item;
#override
Widget build(BuildContext context) {
return Material(
child: ListTile(
title: Text(item.title),
subtitle: Text(item.description),
),
);
}
}
class LoadingItemNotifier extends StatefulWidget {
const LoadingItemNotifier({Key? key, this.isLoading = false})
: super(key: key);
final bool isLoading;
#override
State<LoadingItemNotifier> createState() => _LoadingItemNotifierState();
}
class _LoadingItemNotifierState extends State<LoadingItemNotifier> {
#override
Widget build(BuildContext context) {
if (widget.isLoading) {
return Material(
child: ListTile(
leading: SizedBox(
width: 48,
height: 48,
child: ClipOval(
child: Material(
color: Colors.lightBlue.withOpacity(0.25),
child: const Center(
child: Icon(Icons.download),
),
),
),
),
title: const Text('Loading'),
isThreeLine: true,
subtitle: const Text('One moment please...'),
dense: true,
),
);
} else {
return const SizedBox(height: 0);
}
}
}
class ItemSet extends ChangeNotifier {
final List<Item> _items = [];
late bool isBusyLoading = false;
List<Item> get items => _items;
void add(Item item) {
_items.add(item);
notifyListeners();
}
void remove(Item item) {
_items.remove(item);
notifyListeners();
}
Future<void> fetch() async {
isBusyLoading = true;
notifyListeners();
/* handling REST call here */
await Future.delayed(const Duration(milliseconds: 500));
Item newItem = const Item(id: 123, title: 'Title', description: 'Desc');
isBusyLoading = false;
add(newItem);
}
}
class Item extends Equatable {
const Item({
required this.id,
required this.title,
required this.description,
});
final int id;
final String title;
final String description;
#override
List<Object> get props => [id, title, description];
}
Caveats
I don't know if this is the most efficient way of doing this - perhaps there should be fewer states, etc. ...

Flutter use_build_context_synchronously and SearchDelegate.close after await

My use case: I show a search UI the user can pick an item with - when the user taps an item, something async happens and depending on the result, close (https://api.flutter.dev/flutter/material/SearchDelegate/close.html) is called with either the picked item or a null. In my snippet below the async something is simply asking the user for confirmation with an AlertDialog.
This works without an issue but I updated the lint rules recently, and turned this one on: https://dart-lang.github.io/linter/lints/use_build_context_synchronously.html. Now the linter complains about the BuildContext being used after an await in the call to close. Here is the snippet (full reproducible sample below, written with Flutter 2.10.4):
onTap: () async {
final confirmed = await _confirm(context, item) ?? false;
// Triggers 'Do not use BuildContexts across async gaps.'
// https://dart-lang.github.io/linter/lints/use_build_context_synchronously.html
close(context, confirmed ? item : null);
},
I guess this makes sense and could be dangerous in some scenarios, so I'd better fix it. My question is: how do I implement my use case the 'right' way? One solution that works is to extract suggestions to a separate StatefulWidget and guard the call with a isMounted check, but this has the following drawbacks:
It requires a separate StatefulWidget that I would otherwise not need (boilerplate I don't want).
I need to pass a callback to the widget to call close (it belongs to SearchDelegate but now it will be called by code in the widget).
As close requires a BuildContext, I either have to pass the one that the SearchDelegate has to the widget to use (yuck) or just use the one from my widget - in this case it works (as close just uses Navigator.of(context)), but what if it were necessary to pass exactly the one from SearchDelegate?
Full code:
import 'package:flutter/material.dart';
void main() {
runApp(App());
}
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: _Home(),
);
}
}
class _Home extends StatefulWidget {
#override
State<_Home> createState() => _HomeState();
}
class _HomeState extends State<_Home> {
String? _picked;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
TextButton(
onPressed: () => _maybePick(context),
child: const Text('Maybe pick'),
),
Center(
child: Text(_picked != null ? 'Last pick: $_picked' : 'No pick'),
),
],
),
);
}
Future<void> _maybePick(BuildContext context) async {
final result = await showSearch<String?>(
context: context,
delegate: _PickerDelegate(),
);
if (result != null) {
setState(() {
_picked = result;
});
}
}
}
class _PickerDelegate extends SearchDelegate<String?> {
final _allItems = List.generate(200, (index) => 'Item $index');
#override
Widget buildLeading(BuildContext context) {
return IconButton(
onPressed: () => close(context, null),
icon: const BackButtonIcon(),
);
}
#override
List<Widget> buildActions(BuildContext context) {
return [
IconButton(
onPressed: () => query = '',
icon: const Icon(Icons.clear),
),
];
}
#override
Widget buildSuggestions(BuildContext context) {
final items = _allItems.where((element) => element.contains(query));
return ListView(
children: items.map((item) {
return ListTile(
title: Text(item),
onTap: () async {
final confirmed = await _confirm(context, item) ?? false;
// Triggers 'Do not use BuildContexts across async gaps.'
// https://dart-lang.github.io/linter/lints/use_build_context_synchronously.html
close(context, confirmed ? item : null);
},
);
}).toList(growable: false),
);
}
#override
Widget buildResults(BuildContext context) {
// Keep it simple for the snippet.
throw UnimplementedError('results are not supported');
}
Future<bool?> _confirm(BuildContext context, String item) async {
return showDialog<bool>(
context: context,
builder: (context) {
return AlertDialog(
content: Text("Pick '$item'?"),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(true),
child: const Text('Yes'),
),
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: const Text('No'),
),
],
);
},
);
}
}

Save Multi Select Choice Chips selected/unselected state in an alert-dialog

I am filtering results in my application based off of the multichoice chips that are selected. I have it filtering results properly, however when I select the done button in the alert dialog it does not save the selected state of the choice chips. It also does not clear the selected states of the choice chips when I hit the clear button. Any recommendations?
MultiFilterChoiceChips Class:
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
class MultiFilterChips extends StatefulWidget {
final List<String> filterList;
final Function(List<String>) onSelectionChanged;
MultiFilterChips(this.filterList, {this.onSelectionChanged});
#override
_MultiFilterChipsState createState() => _MultiFilterChipsState();
}
class _MultiFilterChipsState extends State<MultiFilterChips> {
List<String> selectedFilters = List();
_buildFilterList() {
List<Widget> filters = List();
widget.filterList..forEach((item){
filters.add(Container(
padding: const EdgeInsets.all(2.0),
child: ChoiceChip(
label: Text('$item'),
selected: selectedFilters.contains(item),
onSelected: (selected) {
setState(() {
selectedFilters.contains(item)
? selectedFilters.remove(item)
: selectedFilters.add(item);
widget.onSelectionChanged(selectedFilters);
});
},
),
));
});
return filters;
}
#override
Widget build(BuildContext context) {
return Wrap(
children: _buildFilterList(),
);
}
}
Filter Pressed (App Bar Icon) Alert Dialog:
_filterPressed() {
return showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
scrollable: true,
title: Text('Filter Scouts'),
content: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text('Rank:'),
_multiFilterRankChipState(),
Padding(padding: EdgeInsets.all(5)),
Text('Patrol:'),
_multiFilterPatrolChipState(),
],
)),
actions: <Widget>[
FlatButton(
child: Text("Clear"),
onPressed: () {
filter = ""; //ranks filter string to send to the sqlite database
pfilter = ""; //patrol filter string to send to the sqlite database
setState(() {
selectedRanks.clear(); //List<String> that holds the selected ranks
selectedPatrols.clear(); //List<String> that holds the selected patrols
//sends the query to the database and resets the future list builder state
// back to initial state without filters
_searchResults(searchText);
});
},
),
FlatButton(
child: Text("Done"),
onPressed: () {
Navigator.of(context).pop();
})
],
);
});
}
Rank MultiFilter Call:
_multiFilterRankChipState() {
return MultiFilterChips(ranks, onSelectionChanged: (selectedList) {
setState(() {
//selectedList = selectedRanks;
selectedRanks = selectedList;
debugPrint("SELECTED LIST ${selectedRanks.toString()}");
_RanksFilterSet();
});
});
}
For getting the list of Patrols I am getting the distinct list from the sqlite database as the list patrols change overtime thus using a future builder to get the list of strings:
Patrol MultiFilter Call:
_multiFilterPatrolChipState() {
return Container(
child: FutureBuilder<List<String>>(
future: patrols(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return MultiFilterChips(snapshot.data,
onSelectionChanged: (selectedList) {
setState(() {
selectedPatrols = selectedList;
_PatrolFilterSet();
});
});
}
return Container(
alignment: AlignmentDirectional.center,
child: new CircularProgressIndicator(
strokeWidth: 7,
));
},
),
);
}
Let me know if you need more code! Thanks!
You can store the selected items in a Map. In this sample, multi-select mode will start on long press of an item. Multi-select mode will stop when there's no selected items left.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
var selectMode = false;
Map<String, bool> listItemSelected = {
'List 1': false,
'List 2': false,
'List 3': false,
'List 4': false,
'List 5': false,
'List 6': false,
'List 7': false,
'List 8': false,
'List 9': false,
};
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: ListView(
children: listItemSelected.keys.map((key) {
return Card(
child: GestureDetector(
onTap: () {
// if multi-select mode is true, tap should select List item
if (selectMode && listItemSelected.containsValue(true)) {
debugPrint('onTap on $key');
setState(() {
listItemSelected[key] = !listItemSelected[key];
});
} else {
// Stop multi-select mode when there's no more selected List item
debugPrint('selectMode STOP');
selectMode = false;
}
},
// Start List multi-select mode on long press
onLongPress: () {
debugPrint('onLongPress on $key');
if (!selectMode) {
debugPrint('selectMode START');
selectMode = true;
}
setState(() {
listItemSelected[key] = !listItemSelected[key];
});
},
child: Container(
// Change List item color if selected
color: (listItemSelected[key])
? Colors.lightBlueAccent
: Colors.white,
padding: EdgeInsets.all(16.0),
child: Text(key),
),
),
);
}).toList(),
),
),
);
}
}
Demo

Dynamic list of check box tile in alert dialog not working

There is no clear answer on how to implement a checkbox tile in a dialog and set the state to work.
A print statement is working in setting the state of the checkbox is not changing, but other statements are working. Where can I find the answer?
I am using a dialog with multiple check boxes for multi select. Is there another of implementing multiselect in Flutter?
child: TextFormField(
decoration: InputDecoration(
labelText: 'Team Leader',
labelStyle: TextStyle(color: Colors.black)),
controller: teamLeaderController,
enabled: false,
style: TextStyle(color: Colors.black),
),
onTap: () {
showDialog(
context: context,
barrierDismissible: true,
builder: (BuildContext context) {
return CheckBoxDialog(context, teamLeader,
"Choose Team Leader", teamLeaderController, onSubmit);
});
}),
class CheckBoxState extends State<CheckBoxDialog> {
BuildContext context;
List<String> places;
String title;
TextEditingController con;
bool state;
CheckBoxState(this.context, this.places, this.title, this.con);
#override
void initState() {
super.initState();
state = false;
}
#override
Widget build(BuildContext context) {
return new AlertDialog(
title: new Text(title),
content:
Column(children: getMultiSelectOption(context, places, con, state)),
actions: <Widget>[
FlatButton(
child: Text('Cancel'),
onPressed: () {
Navigator.of(context).pop();
}),
FlatButton(
child: Text('Ok'),
onPressed: () {
widget.onSubmit("");
Navigator.of(context).pop();
})
],
);
}
List<Widget> getMultiSelectOption(BuildContext context, List<String> places,
TextEditingController con, bool state) {
List<Widget> options = [];
List<String> selectedList = [];
for (int i = 0; i < places.length; i++) {
options.add(CheckboxListTile(
title: Text(places[i]),
value: selectedList.contains(places[i]),
onChanged: (bool value) {
print("on change: $value title: ${places[i]}");
setState(() {
if (value) {
selectedList.add(places[i]);
} else {
selectedList.remove(places[i]);
}
print("contains: ${selectedList.contains(places[i])}");
print("status: $value");
});
}));
}
return options;
}
}
Suppose you have a Dialog with some Widgets such as RadioListTile, DropdowButton… or anything that might need to be updated WHILE the dialog remains visible, how to do it?
Look at this example here.
https://www.didierboelens.com/2018/05/hint-5-how-to-refresh-the-content-of-a-dialog-via-setstate/
Suppose you have a Dialog with some Widgets such as RadioListTile, DropdowButton… or anything that might need to be updated WHILE the dialog remains visible, how to do it?
Difficulty: Beginner
Background
Lately I had to display a Dialog to let the user select an item from a list and I wanted to display a list of RadioListTile.
I had no problem to show the Dialog and display the list, via the following source code:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
class Sample extends StatefulWidget {
#override
_SampleState createState() => new _SampleState();
}
class _SampleState extends State<Sample> {
List<String> countries = <String>['Belgium','France','Italy','Germany','Spain','Portugal'];
int _selectedCountryIndex = 0;
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_){_showDialog();});
}
_buildList(){
if (countries.length == 0){
return new Container();
}
return new Column(
children: new List<RadioListTile<int>>.generate(
countries.length,
(int index){
return new RadioListTile<int>(
value: index,
groupValue: _selectedCountryIndex,
title: new Text(countries[index]),
onChanged: (int value) {
setState((){
_selectedCountryIndex = value;
});
},
);
}
)
);
}
_showDialog() async{
await showDialog<String>(
context: context,
builder: (BuildContext context){
return new CupertinoAlertDialog(
title: new Text('Please select'),
actions: <Widget>[
new CupertinoDialogAction(
isDestructiveAction: true,
onPressed: (){Navigator.of(context).pop('Cancel');},
child: new Text('Cancel'),
),
new CupertinoDialogAction(
isDestructiveAction: true,
onPressed: (){Navigator.of(context).pop('Accept');},
child: new Text('Accept'),
),
],
content: new SingleChildScrollView(
child: new Material(
child: _buildList(),
),
),
);
},
barrierDismissible: false,
);
}
#override
Widget build(BuildContext context) {
return new Container();
}
}
I was surprised to see that despite the setState in lines #34-36, the selected RadioListTile was not refreshed when the user tapped one of the items.
Explanation
After some investigation, I realized that the setState() refers to the stateful widget in which the setState is invoked. In this example, any call to the setState() rebuilds the view of the Sample Widget, and not the one of the content of the dialog. Therefore, how to do?
Solution
A very simple solution is to create another stateful widget that renders the content of the dialog. Then, any invocation of the setState will rebuild the content of the dialog.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
class Sample extends StatefulWidget {
#override
_SampleState createState() => new _SampleState();
}
class _SampleState extends State<Sample> {
List<String> countries = <String>['Belgium','France','Italy','Germany','Spain','Portugal'];
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_){_showDialog();});
}
_showDialog() async{
await showDialog<String>(
context: context,
builder: (BuildContext context){
return new CupertinoAlertDialog(
title: new Text('Please select'),
actions: <Widget>[
new CupertinoDialogAction(
isDestructiveAction: true,
onPressed: (){Navigator.of(context).pop('Cancel');},
child: new Text('Cancel'),
),
new CupertinoDialogAction(
isDestructiveAction: true,
onPressed: (){Navigator.of(context).pop('Accept');},
child: new Text('Accept'),
),
],
content: new SingleChildScrollView(
child: new Material(
child: new MyDialogContent(countries: countries),
),
),
);
},
barrierDismissible: false,
);
}
#override
Widget build(BuildContext context) {
return new Container();
}
}
class MyDialogContent extends StatefulWidget {
MyDialogContent({
Key key,
this.countries,
}): super(key: key);
final List<String> countries;
#override
_MyDialogContentState createState() => new _MyDialogContentState();
}
class _MyDialogContentState extends State<MyDialogContent> {
int _selectedIndex = 0;
#override
void initState(){
super.initState();
}
_getContent(){
if (widget.countries.length == 0){
return new Container();
}
return new Column(
children: new List<RadioListTile<int>>.generate(
widget.countries.length,
(int index){
return new RadioListTile<int>(
value: index,
groupValue: _selectedIndex,
title: new Text(widget.countries[index]),
onChanged: (int value) {
setState((){
_selectedIndex = value;
});
},
);
}
)
);
}
#override
Widget build(BuildContext context) {
return _getContent();
}
}

How can I handle a list of checkboxes dynamically created in flutter?

Using flutter, I am trying to build a list of values with some text and a customized checkbox next to it. Tapping anywhere on the text or checkbox should show the enabled state and tapping again should disable it. I am unsure how to handle the state of each checkbox separately. I tried using CheckBoxListTile too but I am not sure how I can achieve what I want. Can someone provide any examples?
Here's some sample code for CheckboxListTile. You can find more examples in the gallery.
import 'package:flutter/material.dart';
class Demo extends StatefulWidget {
#override
DemoState createState() => new DemoState();
}
class DemoState extends State<Demo> {
Map<String, bool> values = {
'foo': true,
'bar': false,
};
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(title: new Text('CheckboxListTile demo')),
body: new ListView(
children: values.keys.map((String key) {
return new CheckboxListTile(
title: new Text(key),
value: values[key],
onChanged: (bool value) {
setState(() {
values[key] = value;
});
},
);
}).toList(),
),
);
}
}
void main() {
runApp(new MaterialApp(home: new Demo(), debugShowCheckedModeBanner: false));
}
I think it will work as you want. It also stores all selected checkbox value(s) into a List variable. so you please simply put this code in main.dart file and execute to check how it works.
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'Multi-Select & Unselect Checkbox in Flutter'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List _selecteCategorys = List();
Map<String, dynamic> _categories = {
"responseCode": "1",
"responseText": "List categories.",
"responseBody": [
{"category_id": "5", "category_name": "Barber"},
{"category_id": "3", "category_name": "Carpanter"},
{"category_id": "7", "category_name": "Cook"}
],
"responseTotalResult":
3 // Total result is 3 here becasue we have 3 categories in responseBody.
};
void _onCategorySelected(bool selected, category_id) {
if (selected == true) {
setState(() {
_selecteCategorys.add(category_id);
});
} else {
setState(() {
_selecteCategorys.remove(category_id);
});
}
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: ListView.builder(
itemCount: _categories['responseTotalResult'],
itemBuilder: (BuildContext context, int index) {
return CheckboxListTile(
value: _selecteCategorys
.contains(_categories['responseBody'][index]['category_id']),
onChanged: (bool selected) {
_onCategorySelected(selected,
_categories['responseBody'][index]['category_id']);
},
title: Text(_categories['responseBody'][index]['category_name']),
);
}),
);
}
}
use List contains returns bool.
here is example
List<int> selectedList = [];
List<Widget> mList; //you can't add equal
createMenuWidget(Course courses) {
for (int b = 0; b < courses.length; b++) {
Map cmap = courses[b];
mList.add(CheckboxListTile(
onChanged: (bool value){
setState(() {
if(value){
selectedList.add(cmap[course_id]);
}else{
selectedList.remove(cmap[course_id]);
}
});
},
value: selectedList.contains(cmap[course_id]),
title: new Text(cmap[course_name]),
));
}
}
The simple Way to Do this with Dynamic List of Data with CheckBox.
List<String>data= ["Mathew","Deon","Sara","Yeu"];
List<String> userChecked = [];
ListView.builder(
itemCount: data.length,
itemBuilder: (context, i) {
return ListTile(
title: Text(
data[i])
trailing:Checkbox(
value: userChecked.contains(data[i]),
onChanged: (val) {
_onSelected(val, data[i]);
},
)
//you can use checkboxlistTile too
);
})
// now we write the functionality to check and uncheck it!!
void _onSelected(bool selected, String dataName) {
if (selected == true) {
setState(() {
userChecked.add(dataName);
});
} else {
setState(() {
userChecked.remove(dataName);
});
}
}
And Its Done !!...
Enjoy Fluttering...
Give a thumbs up as it will work for you !! :P
Please use package grouped_buttons.
It support both checkbox and radio.
https://pub.dartlang.org/packages/grouped_buttons
CheckboxGroup(
labels: <String>[
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
],
disabled: [
"Wednesday",
"Friday"
],
onChange: (bool isChecked, String label, int index) => print("isChecked: $isChecked label: $label index: $index"),
onSelected: (List<String> checked) => print("checked: ${checked.toString()}"),
),
and full example of usage in here https://github.com/akshathjain/grouped_buttons/blob/master/example/lib/main.dart
and author's logic to implement
https://github.com/akshathjain/grouped_buttons/blob/master/lib/src/checkbox_group.dart
Basically, author use two List of Strings to control selected and unselect
void onChanged(bool isChecked, int i){
bool isAlreadyContained = _selected.contains(widget.labels.elementAt(i));
if(mounted){
setState(() {
if(!isChecked && isAlreadyContained){
_selected.remove(widget.labels.elementAt(i));
}else if(isChecked && !isAlreadyContained){
_selected.add(widget.labels.elementAt(i));
}
if(widget.onChange != null) widget.onChange(isChecked, widget.labels.elementAt(i), i);
if(widget.onSelected != null) widget.onSelected(_selected);
});
}
}
Screenshot (Null safe)
Code:
class _MyPageState extends State<MyPage> {
final Map<String, bool> _map = {};
int _count = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () => setState(() => _map.addEntries([MapEntry('Checkbox #${++_count}', false)])),
),
body: ListView(
children: _map.keys
.map(
(key) => CheckboxListTile(
value: _map[key],
onChanged: (value) => setState(() => _map[key] = value!),
subtitle: Text(key),
),
)
.toList(),
),
);
}
}
Use chekboxListTile
Here is the sample code
#override
Widget build(BuildContext context) {
return Center(
child: CheckboxListTile(
title: Text('Check me'),
);
}
By the way, You can also add checkbox in ListView in Flutter. Apps sample is given below-
Main.dart
import 'package:flutter/material.dart';
import 'checkbox_in_listview_task-7.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(primarySwatch: Colors.blue,
),
home: CheckBoxInListview(),
);
}
}
checkbox_in_listview_task.dart
import 'package:flutter/material.dart';
class CheckBoxInListview extends StatefulWidget {
#override
_CheckBoxInListviewState createState() => _CheckBoxInListviewState();
}
class _CheckBoxInListviewState extends State<CheckBoxInListview> {
bool _isChecked = true;
List<String> _texts = [
"InduceSmile.com," "Flutter.io",
"google.com",
"youtube.com",
"yahoo.com",
"gmail.com"
];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("CheckBox in ListView Example"),
),
body: ListView(
padding: EdgeInsets.all(8.0),
children: _texts.map((text) => CheckboxListTile(
title: Text(text),
value: _isChecked,
onChanged: (val) {
setState(() {
_isChecked = val;
});
},
)).toList(),
),
);
}
}
And here is the output
here how you can do it
import 'package:flutter/material.dart';
class RegisterFragments extends StatefulWidget {
RegisterFragments({Key key, this.step}) : super(key: key);
final int step;
_RegisterFragmentsState createState() => _RegisterFragmentsState();
}
class _RegisterFragmentsState extends State<RegisterFragments> {
Map<String, bool> values = {"abc": false, "def": true, "ghi": false};
List<String> _do = ['One', 'Two', 'Free', 'Four'];
String _dropdownValue = 'One';
#override
Widget build(BuildContext context) {
switch (widget.step) {
case 0:
return buildDo();
break;
case 1:
return Container(
child: ListView.builder(
shrinkWrap: true,
itemCount: values.length,
itemBuilder: (BuildContext context, int index) {
switch (widget.step) {
case 0:
return buildDo();
break;
case 1:
return buildService(context, index);
break;
default:
return Container();
break;
}
},
),
);
break;
default:
return Container();
break;
}
}
Widget buildService(BuildContext context, int index) {
String _key = values.keys.elementAt(index);
return Container(
child: Card(
child: CheckboxListTile(
title: Text(_key),
onChanged: (bool value) {
setState(() {
values[_key] = value;
});
},
value: values[_key],
),
),
);
}
Widget buildDo() {
return DropdownButton<String>(
isExpanded: true,
hint: Text("Service"),
items: _do.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
onChanged: (String newValue) {
setState(() {
this._dropdownValue = newValue;
});
},
value: _dropdownValue,
);
}
}
In case you are using the CheckBoxGroup or anything similar AND are inside a slider, do not forget to put it in a StatefulBuilder:
return StatefulBuilder(// StatefulBuilder
builder: (context, setState) {
return CheckboxGroup(
orientation: GroupedButtonsOrientation.HORIZONTAL,
margin: const EdgeInsets.only(left: 12.0),
onSelected: (List selected) => setState(() {
_checked = selected;
}),
labels: teamWorkoutDays,
checked: _checked,
itemBuilder: (Checkbox cb, Text txt, int i) {
return Column(
children: <Widget>[
Icon(Icons.polymer),
cb,
txt,
],
);
},
);
});