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,
);
}
}
Related
I can't figure out how to make ListView items choosable like in screenshots:
This should work. On long press using GestureDetector you should alter a property of the model in the list and set state. Showing the checkbox when that condition is true, otherwise use an empty container. You can then do whatever you need within that checkbox specifically.
import 'package:flutter/material.dart';
class TEST extends StatefulWidget {
const TEST({Key? key}) : super(key: key);
#override
State<TEST> createState() => _TESTState();
}
class _TESTState extends State<TEST> {
List<MyItem> _objs = [MyItem(id: '', isSelected: false)];
bool _showCheckboxes = false;
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: _objs.length,
itemBuilder: (context, index) {
var item = _objs[index];
return GestureDetector(
onLongPress: () {
setState(() {
_showCheckboxes = true;
});
},
child: ListTile(
leading: _showCheckboxes
? Checkbox(
value: item.isSelected,
onChanged: (val) {
setState(() {
item.isSelected = !item.isSelected;
});
})
: Container()),
);
},
);
}
}
class MyItem {
String id;
bool isSelected;
///other properties...
MyItem({required this.id, required this.isSelected});
}
Edit: Shows checkboxes for all items, as required in question.
You can give it a leading icon by giving it a ListTile.
I need a DropdownButton with items depending on another DropdownButton. Sounds a bit confusing but it isnt. Here is my code with comments at the important parts in order to understand my intention.
Parent
class Parent extends StatefulWidget {
const Parent({ Key? key }) : super(key: key);
#override
State<Parent> createState() => _ParentState();
}
class _ParentState extends State<Parent> {
#override
Widget build(BuildContext context) {
return SafeArea(
child: SizedBox(
width: 500,
height: 500,
child: Column(
children: const [
// Main
DropDownWidget(collection: "MainCollection",),
// Depending
DropDownWidget(collection: ""), // Collection should equals value from Main DropDownWidget
],
),
),
);
}
}
Child
class DropDownWidget extends StatefulWidget {
final String collection;
const DropDownWidget({Key? key, required this.collection}) : super(key: key);
#override
State<DropDownWidget> createState() => _DropDownWidgetState();
}
class _DropDownWidgetState extends State<DropDownWidget> {
var selectedItem;
#override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance
.collection(widget.collection)
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData || snapshot.hasError) {
return const CircularProgressIndicator();
} else {
var length = snapshot.data?.docs.length;
List<DropdownMenuItem<String>> items = [];
for (int i = 0; i < length!; i++) {
DocumentSnapshot snap = snapshot.data!.docs[i];
items.add(DropdownMenuItem(
child: Text(snap.id),
value: snap.id,
));
}
return DropdownButtonFormField<String>(
onChanged: (value) {
setState(() {
selectedItem = value;
// ********************
// PASS value TO PARENT
// ********************
});
},
value: selectedItem,
items: items);
}
});
}
}
When the Main DropdownButton changes its value, it should pass that to my parent in order to change the focused collection of my depending DropdownButton. I already solved that problem by throwing all the code in one class buts that not the way I want to go.
So maybe you can help me out :)
Thanks
Create an argument ValueChanged<String> onSelectItem in your child. Call the method when the value changes.
Then in your parent, you provide a function that needs to be called when the value changes in your child.
Im trying to use Lift the State for my Checkbox and I know that by changing my TaskTile to a stateful widget ill be able to solve my problem but I want to know why my Checkbox isnt updating when I pass the setstate as a callback from a stateful widget.Im sorry if this question is dumb im just getting started with Flutter so any help is appreciated.
This is my stateless widget which builds the checkbox in a tile
import 'package:flutter/material.dart';
class TaskTile extends StatelessWidget {
final String title;
final bool isChecked;
final void Function(bool?) checkboxCallback;
TaskTile({required this.title,required this.isChecked,required this.checkboxCallback});
#override
Widget build(BuildContext context) {
return ListTile(
title: Text(title,style: TextStyle(decoration: isChecked?TextDecoration.lineThrough:null),),
trailing: Checkbox(
value: isChecked,
onChanged: checkboxCallback,
),
);
}
}
And this is the stateful widget where I am giving the callback
import 'package:flutter/material.dart';
import 'task_tile.dart';
import 'task.dart';
class TaskList extends StatefulWidget {
#override
_TaskListState createState() => _TaskListState();
}
class _TaskListState extends State<TaskList> {
#override
Widget build(BuildContext context) {
bool isChecked = false;
List<Task> tasks = [
Task(title: 'Buy Milk', isDone: isChecked),
Task(title: 'Buy Bread', isDone: isChecked),
Task(title: 'Buy Eggs', isDone: isChecked),
];
return ListView.builder(itemBuilder: (context, index) {
return TaskTile(title: tasks[index].title,isChecked: tasks[index].isDone,checkboxCallback: (checkboxState){
setState(() {
tasks[index].toggleIsDone();
});
},);
},itemCount: tasks.length,);
}
}
Also this is the Task Class where I am giving the toggle function to change the state
class Task{
final String title;
bool isDone;
Task({required this.title,required this.isDone});
void toggleIsDone(){
isDone = !isDone;
}
}
You are not calling the function.
Instead of
trailing: Checkbox(
value: isChecked,
onChanged: checkboxCallback,
),
Can you try
trailing: Checkbox(
value: isChecked,
onChanged: (){checkboxCallback();},
),
You need to make TaskTile statefull. TaskList can be StatelessWidget.
Here is the answer:
import 'package:flutter/material.dart';
class TaskList extends StatelessWidget {
#override
Widget build(BuildContext context) {
bool isChecked = false;
List<Task> tasks = [
Task(title: 'Buy Milk', isDone: isChecked),
Task(title: 'Buy Bread', isDone: isChecked),
Task(title: 'Buy Eggs', isDone: isChecked),
];
return ListView.builder(
itemBuilder: (context, index) {
return TaskTile(
title: tasks[index].title,
isChecked: tasks[index].isDone,
checkboxCallback: (value) {
tasks[index].toggleIsDone();
},
);
},
itemCount: tasks.length,
);
}
}
class TaskTile extends StatefulWidget {
final String title;
final bool isChecked;
final Function checkboxCallback;
TaskTile(
{required this.title,
required this.isChecked,
required this.checkboxCallback,
Key? key})
: super(key: key);
#override
_TaskTileState createState() => _TaskTileState();
}
class _TaskTileState extends State<TaskTile> {
late bool _isChecked;
#override
void initState() {
super.initState();
_isChecked = widget.isChecked;
}
#override
Widget build(BuildContext context) {
return ListTile(
title: Text(
widget.title,
style: TextStyle(
decoration: _isChecked ? TextDecoration.lineThrough : null),
),
trailing: Checkbox(
value: _isChecked,
onChanged: (value) {
widget.checkboxCallback(value);
setState(() {
_isChecked = value!;
});
},
),
);
}
}
class Task {
final String title;
bool isDone;
Task({required this.title, required this.isDone});
void toggleIsDone() {
isDone = !isDone;
}
}
For any dynamic change your widget class need to be stateful, if you are using vscode than you can easily change from stateless to statful . First hover to your statless widget and than you will get a bulb like indicator , press it than you will get an option of changing the state.
According to my understanding, You are trying to change the state (isChanged) of the Task object in a list. This list inturn is used in listview builder. Since flutter work with immutable data and you are sending the reference of the list to the listview builder, the changes of the element in the list wont be reflected in the UI, eventhough you used setState function. Instead try creating a new list with the same name with the updated element. This will definitely work.
I got the idea from this solution - https://stackoverflow.com/a/61847327/11844877
PS
I am a newbie to flutter and don't mind if my answer wasn't helpful.
This is the solution from the link below:
import 'package:flutter/material.dart';
class TaskList extends StatelessWidget {
#override
Widget build(BuildContext context) {
bool isChecked = false;
List<Task> tasks = [
Task(title: 'Buy Milk', isDone: isChecked),
Task(title: 'Buy Bread', isDone: isChecked),
Task(title: 'Buy Eggs', isDone: isChecked),
];
return ListView.builder(
itemBuilder: (context, index) {
return TaskTile(
title: tasks[index].title,
isChecked: tasks[index].isDone,
checkboxCallback: (value) {
tasks[index].toggleIsDone();
},
);
},
itemCount: tasks.length,
);
}
}
class TaskTile extends StatefulWidget {
final String title;
final bool isChecked;
final Function checkboxCallback;
TaskTile(
{required this.title,
required this.isChecked,
required this.checkboxCallback,
Key? key})
: super(key: key);
#override
_TaskTileState createState() => _TaskTileState();
}
class _TaskTileState extends State<TaskTile> {
late bool _isChecked;
#override
void initState() {
super.initState();
_isChecked = widget.isChecked;
}
#override
Widget build(BuildContext context) {
return ListTile(
title: Text(
widget.title,
style: TextStyle(
decoration: _isChecked ? TextDecoration.lineThrough : null),
),
trailing: Checkbox(
value: _isChecked,
onChanged: (value) {
widget.checkboxCallback(value);
setState(() {
_isChecked = value!;
});
},
),
);
}
}
class Task {
final String title;
bool isDone;
Task({required this.title, required this.isDone});
void toggleIsDone() {
isDone = !isDone;
}
}
https://codeutility.org/why-is-my-checkbox-not-updating-when-i-pass-a-callback-from-a-stateful-widget-to-stateless-widget/
I have created a filter to filter UI between two dates. My problem is that the UI does not accurately reflect this, instead I notice that the former filteredList is used BUT the build .length method calculates accurately... resulting in displaying the wrong items from the list.
See code below, simplified for convenience:
homepage.dart
class _MyHomePageState extends State<HomePage> {
void _refreshFilter() {
setState(() => filteredList = myList.where((item) {
DateTime date = DateTime(item.date.year, item.date.month, item.date.day);
return date != null && (dateFilterFrom != null ? date.difference(dateFilterFrom).inDays >= 0: true) &&
(dateFilterTo != null ? dateFilterTo.difference(date).inDays >= 0: true);})
.toList());
showSnackBar(context, 'Filter refreshed!');
#override
Widget build(BuildContext context) {
return Display(filteredList);
}
display.dart
class Display extends StatefulWidget {
const Display(this.filteredList, {Key key})
: super(key: key);
final List filteredList;
#override
_DisplayState createState() => _DisplayState();
}
class _DisplayState extends State<Display> {
#override
Widget build(BuildContext context) {
return Flexible(
child: Padding(
padding: EdgeInsets.all(20.0),
child: ListView.builder(
shrinkWrap: true,
itemCount: filteredList.length,
itemBuilder: (context, index) {
return MyItem(index),
));
}
});
}
myitem.dart
class MyItem extends StatefulWidget {
MyItem(this.index, {Key key}) : super(key: key);
final int index;
#override
_MyItemState createState() => _MyItemState();
}
class _MyItemState extends State<MyItem> {
DateTime dateTime;
#override
Widget build(BuildContext context) {
return OutlineButton(
borderSide: BorderSide(color: Colors.blue),
onPressed: () {
DatePicker.showDateTimePicker(context, showTitleActions: true,
onChanged: (value) {
print('change $value in time zone ' +
value.timeZoneOffset.inHours.toString());
}, onConfirm: (value) {
print('confirm $value');
setState(() {
dateTime = value;
filteredList[widget.index].date = value;
});
}, currentTime: dateTime != null ? dateTime : DateTime.now());
},
child: Text(
dateTime != null
? dateTime.toString().substring(0, 16)
: "select date",
style: TextStyle(color: Colors.blue),
));
}
}
filteredList is in globals.dart
Any tips strongly appreciated, or simply a method to have a parent widget with function that will rebuild the builder from the child widget...
NB: When I debug filteredList print the accurate items, so this is definitely a UI/state issue
In your Display widget class, add required List parameter, something like this:
class Display extends Stateless {
final List filteredList;
const Display({required this.filteredList});
Now, in your homepage where you callDisplay(), it'll ask you for the list,
So, it will look like this now:
Widget build(BuildContext context) {
return Display(filteredList: filteredList);
This way, you can convert your listviewBuilder to be in a stateless widget, where you get better performance. And from your homepage, whenever you press the filter function, it'll rebuild your Display widget, with actual filtered list.
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