I have a widget who need to select a single item using Radio as dynamically. I already created that widget like below:
int number;
return Container(
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Radio(
value: index,
groupValue: number,
activeColor: Color(0xFFE91E63),
onChanged: (int val) {
setState(() {
number = val;
print('Show the Resumes $number');
});
},
),
Text(
'Show',
),
],
),
);
I looping the above widget inside a ListView.builder. And the index in the value is from index from itemBuilder on ListView.builder. And when I run the code, it looks like this.
So how to make my Radio is only select a single item?
Maybe you can write like this, declare value and groupValue outside the loop (builder in ListView). And create value, groupValue, and onChanged in the constructor. And the result like this.
...
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
List<int> _numbers = List<int>.generate(5, (index) => index);
int _groupNumber;
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
itemBuilder: (BuildContext context, int index) {
return HomeContent(
value: _numbers[index],
groupValue: _groupNumber,
onChanged: (int value) {
setState(() {
_groupNumber = value;
});
},
);
},
itemCount: _numbers.length,
),
);
}
}
class HomeContent extends StatelessWidget {
final int value;
final int groupValue;
final ValueChanged<int> onChanged;
const HomeContent({
Key key,
this.value,
this.groupValue,
this.onChanged,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return RadioListTile<int>(
value: this.value,
groupValue: this.groupValue,
onChanged: this.onChanged,
title: Text('Value $value On Group $groupValue'),
);
}
}
Change 'groupValue: number' to
final var _groupValue = -1;
return Container(
...
groupValue: _groupValue,
...
);
and show Trouble with flutter radio button
Related
The two places highlighted are the cause of the problem. In the image as shown below, after I add a task, I am not able to individually select a task, instead all the tasks that I have added get selected collectively. How do I fix this to just select the task that I click on?
This is the Tasks class that extends the ChangeNotifier:
class Tasks extends ChangeNotifier {
bool value = false;
List<String> taskList = [
'Buy Milk',
];
void addTask(String newTask) {
taskList.add(newTask);
notifyListeners();
}
}
This is the updated entire tasks.dart file:
class TaskList extends StatelessWidget {
const TaskList({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Consumer<Tasks>(
builder: (context, value, child) {
return ListView.builder(
itemCount: value.taskList.length,
itemBuilder: (context, index) {
return TaskTile(
listText: value.taskList[index],
functionCallback: (newValue) {}, //Enter Function Here.
);
},
);
},
);
}
}
class TaskTile extends StatelessWidget {
String? listText;
Function(bool?)? functionCallback;
TaskTile({this.listText, this.functionCallback, Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return CheckboxListTile(
title: Text(
listText!,
style: TextStyle(
decoration: Provider.of<Tasks>(context, listen: false).boolValue
? TextDecoration.lineThrough
: null,
),
),
activeColor: Colors.black,
value: Provider.of<Tasks>(context, listen: false).boolValue,
onChanged: functionCallback,
);
}
}
The actual problem is that you are using the same boolean value for all the check boxes' state(true/false[weather its selected or not]).
So, when you click on one checkbox it sets the value of value(variable) to true and therefore all the checkboxes read the value from the common value (which becomes true).Therefore,every box gets selected.
Solution : You may use different variables for different check boxes' state(true/false) if the number of checkboxes is limited,otherwise go for a differnet approach.
You are getting the whole class when you call provider.
In addition, value is a global variable for the class itself, not for the items inside taskList.
So if you need to modify a Task individually you can do something like this:
class Tasks extends ChangeNotifier {
bool value = false;
List<Task> taskList = [
Task('Buy Milk'),
];
void addTask(Task newTask) {
taskList.add(newTask);
notifyListeners();
}
void deltaValue(bool b, int index) {
taskList[index].value = !taskList[index].value; // Individual task value
notifyListeners();
}
}
Instead of using a List of String you can create a new class called Task to store the values:
class Task extends ChangeNotifier {
String name;
bool value = false;
Task(this.name);
}
The last step would be to use a Widget that displays all the values stored on the List.
For example you can use ListView.builder, so you have the index and you can use it to modify the individual value of a Task:
class TaskTile extends StatelessWidget {
String? listText;
TaskTile({this.listText, Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final tasks = Provider.of<Tasks>(context, listen: false);
final taskList = tasks.taskList; //ListView helps you iterate over all the elements on the list
return ListView.builder(
itemCount: taskList.length,
itemBuilder: (context, index) {
final task = taskList[index];
return CheckboxListTile(
title: Text(
listText!,
style: TextStyle(
decoration: task.value
? TextDecoration.lineThrough
: null,
),
),
activeColor: Colors.black,
value: task.value,
onChanged: (newValue) {
Provider.of<Tasks>(context, listen: false)
.deltaValue(newValue!,index); //Problem Here.
},
);
});
}
}
The method I used was that I created an extra map in the Tasks class and defined a map called taskMap and used the strings defined in the taskList and the bool value to control TaskTile.
The addTask function is used when adding tasks to the taskList elsewhere in the program, but it also adds tasks to the taskMap.
The Tasks class:
class Tasks extends ChangeNotifier {
String? task;
List<String> taskList = [
'Buy Milk',
];
Map<String, bool> taskMap = {
'Buy Milk': false,
};
void addTask(String newTask) {
taskList.add(newTask);
taskMap[newTask] = false;
notifyListeners();
}
void deltaValue(String newTask) {
taskMap[newTask] = !taskMap[newTask]!;
notifyListeners();
}
}
The entire tasks.dart file:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:todoey_flutter/main.dart';
class TaskList extends StatelessWidget {
const TaskList({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Consumer<Tasks>(
builder: (context, value, child) {
return ListView.builder(
itemCount: value.taskList.length,
itemBuilder: (context, index) {
return TaskTile(
listText: value.taskList[index],
functionCallback: (newValue) {
value.deltaValue(value.taskList[index]);
},
);
},
);
},
);
}
}
class TaskTile extends StatelessWidget {
String? listText;
Function(bool?)? functionCallback;
TaskTile({this.listText, this.functionCallback, Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return CheckboxListTile(
title: Text(
listText!,
style: TextStyle(
decoration:
Provider.of<Tasks>(context, listen: false).taskMap[listText]!
? TextDecoration.lineThrough
: null, //Bool value defined in the taskMap used.
),
),
activeColor: Colors.black,
value: Provider.of<Tasks>(context, listen: false).taskMap[listText], //Bool value defined in the taskMap used.
onChanged: functionCallback,
);
}
}
TO SOLVE THIS U NEED TO RUN IT FIRST
Only for them who have experience with DropDownButton and T typepassing can solve this.
Please Help!
import 'package:flutter/material.dart';
// ignore: must_be_immutable
class SupDropDownButton<T> extends StatefulWidget {
FormFieldValidator<T>? validator;
ValueChanged<T> value;
final List<T> data;
SupDropDownButton(
{Key? key, required this.data, this.validator, required this.value})
: super(key: key);
#override
State<SupDropDownButton> createState() => _SupDropDownButtonState<T>();
}
class _SupDropDownButtonState<T> extends State<SupDropDownButton> {
T? _value;
List<DropdownMenuItem<T>> items() =>
widget.data.cast<T>().map<DropdownMenuItem<T>>(menuItem).toList();
DropdownMenuItem<T> menuItem(dynamic value) => DropdownMenuItem<T>(
value: value,
child: Text(value.name),
);
#override
Widget build(BuildContext context) {
return DropdownButtonFormField<T>(
decoration: const InputDecoration(border: InputBorder.none),
validator: widget.validator,
value: _value,
onChanged: (T? val) {
FocusScope.of(context).requestFocus(FocusNode());
_value = val!;
widget.value.call(val);
setState(() {});
},
items: items(),
hint: const Text('Please select Categories'),
);
}
}
THIS IS THE ERROR
Expected a value of type ((dynamic) => String?)?, but got one of type (Employee) => String?
I have worked on your code. Instead of value.name in your code, I have directly add List<String> for easy reference and it's working fine.I using null safety, that's why add late initialize to data list.if you need to create object and insert JSON data means revert back will rework on it
static data added like List<String> employeeList = ['hari','chetanPatil'] and cast it to data;
Working code :
// ignore: must_be_immutable
class SupDropDownButton<T> extends StatefulWidget {
FormFieldValidator<T>? validator;
ValueChanged<T> value;
late List<T> data;
SupDropDownButton(
{Key? key, required this.data, this.validator, required this.value})
: super(key: key);
#override
State<SupDropDownButton> createState() => _SupDropDownButtonState<T>();
}
class _SupDropDownButtonState<T> extends State<SupDropDownButton> {
T? _value;
List<String> employeeList = ['hari', 'chetanPatil'];
bool isOnLoad = true;
#override
void initState() {
super.initState();
}
List<DropdownMenuItem<T>> items() =>
widget.data.cast<T>().map<DropdownMenuItem<T>>(menuItem).toList();
DropdownMenuItem<T> menuItem(dynamic value) => DropdownMenuItem<T>(
value: value,
child: Text(value),
);
#override
Widget build(BuildContext context) {
widget.data.clear();
widget.data.addAll(employeeList);
return Scaffold(
body: Center(
child: Padding(
padding: const EdgeInsets.all(40.0),
child: DropdownButtonFormField<T>(
decoration: const InputDecoration(border: InputBorder.none),
validator: widget.validator,
value: _value,
onChanged: (T? val) {
FocusScope.of(context).requestFocus(FocusNode());
_value = val!;
print(_value);
widget.value.call(val);
setState(() {});
},
items: items(),
hint: const Text('Please select Categories'),
),
),
),
);
}
}
I have a listview.builder in flutter and every item of the list has a dropdown now whenever I select one dropdown value of every dropdown changes. how can I fix this problem in flutter?
Ok, after spending a couple of hours on this and not finding a satisfactory answer (but a lot of hints) I worked it out.
I made a new StatefulWidget class that wraps the DropdownButton. It is instantiated with the List of items for the dropdown.
listview_dropdownbutton.dart
import 'package:flutter/material.dart';
class ListviewDropdownButton extends StatefulWidget {
final List<dynamic> sizes;
const ListviewDropdownButton({
Key? key,
required this.sizes,
}) : super(key: key);
#override
State<ListviewDropdownButton> createState() => _ListviewDropdownButton();
}
class _ListviewDropdownButton extends State<ListviewDropdownButton> {
List<dynamic>? _sizes;
String _currentSize = '';
#override
Widget build(BuildContext context) {
_sizes = _sizes ?? widget.sizes;
_currentSize = _currentSize != '' ? _currentSize : widget.sizes[0];
return DropdownButton<dynamic>(
value: _currentSize,
style: const TextStyle(
color: Colors.green,
),
items: _sizes!.map<DropdownMenuItem<dynamic>>((dynamic size) {
return DropdownMenuItem(
value: size,
child: Text(size),
);
}).toList(),
onChanged: (dynamic size) {
if (_currentSize != size) {
setState(() {
_currentSize = size!;
});
}
},
);
}
}
In the parent widget, just include the class and use it where you'd put the DropdownButton.
Here's a working example.
main.dart
import 'package:flutter/material.dart';
import 'listview_dropdownbutton.dart';
void main() => runApp(const DropdownButtonApp());
class DropdownButtonApp extends StatelessWidget {
const DropdownButtonApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('DropdownButton In ListView')),
body: Center(
child: DropdownButtonExample(),
),
),
);
}
}
class DropdownButtonExample extends StatelessWidget {
DropdownButtonExample({super.key});
final List<String> _items = <String>['Shirt', 'T-Shirt', 'Pants', 'Blouse', 'Coat'];
final List<String> _sizes = <String>['Small', 'Medium', 'Large', 'X-Large'];
String _currentSize = 'Small';
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: _items.length,
itemBuilder: (
BuildContext context,
int index,
) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(_items[index]),
Row(
children: [
ListviewDropdownButton(
sizes: _sizes,
),
DropdownButton<String>(
value: _currentSize,
style: const TextStyle(
color: Colors.red,
),
items: _sizes.map<DropdownMenuItem<String>>((String size) {
return DropdownMenuItem(
value: size,
child: Text(size),
);
}).toList(),
onChanged: (String? size) {
if (_currentSize != size) {
// setState(() {
_currentSize = size!;
// });
}
},
),
],
),
const Divider(
thickness: 2,
height: 2,
),
],
);
},
);
}
}
To illustrate it works, I put both the ListviewDropdownButton and a regular DropdownButton in the ListView.
I added String _currentSize = 'Small'; and the onChanged method to show the regular DropdownButton does not work. It never changes from "Small", which was my original problem.
So I am struggling with the DropdownButtonFormField where when you change the value it runs the onChange function with the updated value. However, once the onChange finishes the value variable seems to reset itself meaning it never changes.
This is a cut-down version of the full form:
final _formKey = GlobalKey<FormState>();
TextEditingController assetGroupNameController = new TextEditingController();
TextEditingController assetGroupDescriptionController = new TextEditingController();
String assetGroupTypeController;
Widget build(BuildContext context) {
ProgressDialog pr;
assetGroupNameController.text = widget.assetGroup.name;
assetGroupDescriptionController.text = widget.assetGroup.description;
assetGroupTypeController = widget.assetGroup.type;
return ListView(
children: <Widget>[
Card(
elevation: 13.0,
child: Form(
key: _formKey,
child: DropdownButtonFormField(
value: assetGroupTypeController,
items: assetGroupTypes.map((f) {
return new DropdownMenuItem<String>(
value: f['key'],
child: new Text(f['text']),
);
}).toList(),
onChanged: (value) {
typeDropdownChange(value);
})
)
)
);
}
void typeDropdownChange(value) {
setState(() {
assetGroupTypeController = value;
});
}
You assigned the controller directly to value parameter of DropdownButtonFormField and you have string value for DropdownMenuItem. You should be storing the same data type value. Check below example and modify your code accordingly
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Material(
child: Center(
child: new MyDropDown(),
),
),
);
}
}
class MyDropDown extends StatefulWidget {
const MyDropDown({
Key key,
}) : super(key: key);
#override
_MyDropDownState createState() => _MyDropDownState();
}
class _MyDropDownState extends State<MyDropDown> {
String selected;
#override
Widget build(BuildContext context) {
return DropdownButtonFormField<String>(
value: selected,
items: ["Item 1", "Item 2", "Item 3"]
.map((label) => DropdownMenuItem<String>(
child: Text(label),
value: label,
))
.toList(),
onChanged: (value) {
setState(() => selected = value);
},
);
}
}
I have a ListView builder that creates a few ListTitle's with a checkbox inside them.
when I setState on the onChanged on a checkbox, the value doesn't seem to change.
class ProjectPage extends StatefulWidget {
final project;
ProjectPage({Key key, this.project}) : super(key: key);
#override
_ProjectPageState createState() => new _ProjectPageState();
}
class _ProjectPageState extends State<ProjectPage> {
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new Container(
child: Column(
children: <Widget>[
new Expanded(
child: new ListView.builder(
itemBuilder: (BuildContext context, int index) => new ItemsItem(item: widget.project.items[index]),
itemCount: widget.project.items.length,
),
),
],
),
),
);
}
}
class ItemsItem extends StatefulWidget {
final item;
ItemsItem({Key key, this.item}) : super(key: key);
#override
_ItemsItemState createState() => new _ItemsItemState();
}
class _ItemsItemState extends State<ItemsItem> {
final GlobalKey<ScaffoldState> _mainState = new GlobalKey<ScaffoldState>();
#override
Widget build(BuildContext context) {
bool _isCompleted = widget.item.isCompleted;
return new ListTile(
key: _mainState,
title: new Row(
children: <Widget>[
new Expanded(child: new Text(widget.item.name)),
new Checkbox(
value: _isCompleted,
onChanged: (bool newValue) {
setState(() {
_isCompleted = newValue;
});
},
),
],
),
);
}
}
this doesn't seem to change the value
setState(() {
_isCompleted = newValue;
});
any ideas?
edit: Item class
class Item {
final int id;
final String name;
final bool isCompleted;
Item({
this.id,
this.name,
this.isCompleted,
});
Item.fromJson(Map json)
: id = json['id'],
name = json['name'],
isCompleted = json['isCompleted'],
set isCompleted(bool value) {
isCompleted = value;
}
}
_isCompleted is a local variable inside the build method. When the Checkbox's state changes you set the local variable to the new value. setState results in the build method being called again which fetches the old and unchanged value from widget.item.isCompleted. You need to set widget.item.isCompleted to the new changed value:
setState(() {
widget.item.isCompleted = newValue;
});
Btw since your ItemsItem is just a ListTile containing a row with a Text and a Checkbox you should rather use the built-in widget CheckboxListTile