Flutter: `notifyListeners` not updating the list - list

I am trying to list categories of products in my app, and i am using provider package for state management. During the build time its shows the list is empty even though it is not, then I add a click event to update the list that time it works.
I call the getAllCategories() function during the splash screen and the vallCategoryList has values.
This is my category class
class Category with ChangeNotifier{
int catId;
String catName;
String catThumbnail;
List<SetCategory> allCategoryList = [];
void getAllCategories() async {
String categoryUrl = 'https://app.ecwid.com/api/ccccccc';
Response allCategory = await get(categoryUrl);
print('getAllCategories');
if (allCategory.statusCode == 200) {
var categoryData = allCategory.body;
int totalcount = jsonDecode(categoryData)['count'];
if (allCategoryList.length != totalcount) {
allCategoryList.clear();
for (int i = 0; i < totalcount; i++) {
allCategoryList.add(SetCategory(
catId: jsonDecode(categoryData)['items'][i]['id'],
catName: jsonDecode(categoryData)['items'][i]['name'],
catThumbnail: jsonDecode(categoryData)['items'][i]['thumbnailUrl'],
));
}
}
}
print('allcategorylist length ${allCategoryList.length}');
notifyListeners();
}
}
class SetCategory {
int catId;
String catName;
String catThumbnail;
SetCategory(
{ this.catId, this.catName, this.catThumbnail});
}
My code for screen
class HomeScreen extends StatefulWidget {
static const String id = 'homeScreen';
// static int reload = 0;
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
#override
Widget build(BuildContext context) {
final category = Provider.of<Category>(context);
print('category length ${category.allCategoryList.length}'); // here it shows length as 0 even though it has a value of 16.
return Row(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'Category ${category.allCategoryList.length}',
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.bold,
fontSize: 15.0,
),
textAlign: TextAlign.start,
),
),
InkWell(
onTap: () {
category.getAllCategories(); // when i tap here this updates the list
}),
],
),

Did you use ChangeNotifierProvider in your Widget as shown here
If you just used Provider it is not updating but just makes the object accessible from the descendants

Solved by adding a Consumer, changed code like this
child: Consumer<Category>(
builder: (_,category,__){
return ListView.builder();
}

Related

How to display tasks that are not "checked" on the other screen?

I am looking at my code and wondering for 2 hours now without luck so I will ask for help here.
I have a button, when I press it, it displays a random item from the list view. The problem is I also have a check box on the list view with each item. I do not want it to (Shuffle through the items with the checkbox ticked) only to shuffle through the Task in the list view that are unchecked/unticked/are not done.
Here is my code
class TaskData extends ChangeNotifier {
List<Task> _tasks = [
Task(name: "item1"),
Task(name: "item2"),
Task(name: "item3"),
];
UnmodifiableListView<Task> get tasks {
return UnmodifiableListView(_tasks);
}
int get taskCount {
return _tasks.length;
}
// <<Here is the code that shuffles through list
Future<String> rann() async {
return (_tasks.toList()..shuffle()).first.name;
}
void addTask(String newTaskTitle) {
final task = Task(name: newTaskTitle);
_tasks.add(task);
notifyListeners();
}
void updateTask(Task task) {
task.toggleDone();
notifyListeners();
}
In another script I have this one
class Task {
final String name;
bool isDone;
Task({required this.name, this.isDone = false});
void toggleDone() {
isDone = !isDone;
}
}
In another script file I have this code
Padding(
padding:
const EdgeInsets.symmetric(horizontal: 20, vertical: 0),
child: FutureBuilder(
future: Provider.of<TaskData>(context).rann(),
builder: (context, snapshot) {
return Align(
alignment: Alignment.center,
child: Text(
"${snapshot.data}",
//softWrap: true,
textAlign: TextAlign.center,
//textWidthBasis: TextWidthBasis.longestLine,
style: TextStyle(
color: Colors.white,
fontSize: 30,
fontWeight: FontWeight.w700),
),
);
},
),
),
In another script I have this one
class TasksList extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Consumer<TaskData>(
builder: (context, taskData, child) {
return ListView.builder(
itemBuilder: (context, index) {
final task = taskData.tasks[index];
return TaskTile(
taskTitle: task.name,
isChecked: task.isDone,
checkboxCallback: (checkboxState) {
taskData.updateTask(task);
},
);
},
itemCount: taskData.taskCount,
);
},
);
}
}
Any help would be appreciated!
Edit : I also forgot to include this part of code
class TaskTile extends StatelessWidget {
final bool isChecked;
final String taskTitle;
final Function(bool?) checkboxCallback;
final VoidCallback longPressCallback;
TaskTile(
{required this.isChecked,
required this.taskTitle,
required this.checkboxCallback,
required this.longPressCallback});
#override
Widget build(BuildContext context) {
return ListTile(
onLongPress: longPressCallback,
title: Text(
taskTitle,
// at the bottom, it sets decoration of text if isChecked is true, if its not its null
style: TextStyle(
decoration: isChecked ? TextDecoration.lineThrough : null),
),
trailing: Checkbox(
activeColor: Colors.blue,
value: isChecked,
onChanged: checkboxCallback,
),
);
}
}
updated:
class TaskData extends ChangeNotifier {
List<Task> _undoneTasksShuffled = []
// you don't need anymore the rann method() instead you should iterate over this listView
UnmodifiableListView<Task> get undoneTasksShuffled => UnmodifiableListView<Task>(_undoneTasksShuffled);
#override
void notifyListeners() {
//this updates _undoneTasksShuffled every time you call notifyListeners
_undoneTasksShuffled = _tasks.where((e)=> !e.isDone).toList()..shuffle();
super.notifyListeners();
}
...
}
I think you only need to filter the results before get a random element. you need to modify your rann method for something like
//you don't really need a future method because you don't have async code
String rann() {
final r = Random();
final undoneTasks = _tasks.where((e)=> !e.isDone).toList();
//this is for avoid RangeException on list. you can return any other thing
if(undoneTasks.isEmpty) return '';
// i think that you don't really need to shuffle whole list, you only need a random element
return undoneTasks[r.nextInt(undoneTasks.length)].name;
}
i hope this solves your question

Remove User Defined Item from List

Hi I wanted to remove the Data from my List during onTap but I am unable to do so.
This is the code:
Widget buildUser(BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot){
return ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index){
DocumentSnapshot user = snapshot.data.documents[index];
final style = _selectedBusStop.contains(ListClass(user.data['BusstopName'], user.data['location'].latitude.toString(), user.data['location'].longitude.toString()))
? TextStyle(
fontSize: 18,
color: Colors.blueAccent,
fontWeight: FontWeight.bold,
): TextStyle(fontSize: 18);
return ListTile(
leading: CircleAvatar(
backgroundImage: NetworkImage(
user.data['image']
),
),
title: Text(user.data['BusstopName'], style: style),
subtitle: Text('Operating Hour: ' + user.data['OperatingHour'], style: style),
trailing:
_selectedBusStop.contains((ListClass(user.data['BusstopName'], user.data['location'].latitude.toString(), user.data['location'].longitude.toString()))) ? Icon(Icons.check, color: Colors.blueAccent, size: 26) : null,
onTap: (){
if(_selectedBusStop.contains(ListClass(user.data['BusstopName'], user.data['location'].latitude.toString(), user.data['location'].longitude.toString()))){
setState(() {
_selectedBusStop.removeWhere((val) => val == ListClass(user.data['BusstopName'], user.data['location'].latitude.toString(), user.data['location'].longitude.toString()));
print (_selectedBusStop);
});
}
},
onLongPress: (){
setState(() {
_selectedBusStop.add(ListClass(user.data['BusstopName'], user.data['location'].latitude.toString(), user.data['location'].longitude.toString()));
print(_selectedBusStop);
});
}
);
},
);
}
This is the Class:
class ListClass{
String Bname;
String Blat;
String Blng;
ListClass(this.Bname, this.Blat, this.Blng);
#override
String toString(){
return '{${this.Bname}, ${this.Blat}, ${this.Blng}}';
}
}
Any idea where went wrong? Thank you in advance.
Update
List _selectedBusStop = [];
_selectedBusStop is empty List and upon LongPress it will add data into the List and upon onPress it will remove the data if the data already exist in the List.
#randomstudent the issue is the instance, when you comaparing two values with different instance but even value is same, it returns false,
for example to understand simply.
void main() {
final a = IntTestWrapper(3);
final b = IntTestWrapper(3);
print(a==b);
}
class IntTestWrapper {
IntTestWrapper(this.a);
final int a;
}
Output: false
If you want to compare, compare using equatable
if you change like this
class IntTestWrapper extends Equatable {
IntTestWrapper(this.a);
final int a;
#override
List<Object> get props => [a];
}
then for this
void main() {
final a = IntTestWrapper(3);
final b = IntTestWrapper(3);
print(a==b);
}
Output will be true.
To print you can override toString
void main() {
final a = IntTestWrapper(3);
final b = IntTestWrapper(3);
print(a);
}
class IntTestWrapper extends Equatable {
IntTestWrapper(this.a);
final int a;
#override
List<Object> get props => [a];
#override
String toString() => 'The value of a is :$a';
}
Output: The value of a is :3

How to send a List object through a callback function in Flutter?

What I've already done
I have a stateful widget that generates a ListView on screen.
class AppListView extends StatefulWidget {
final ValueChanged onChange;
final List<MatchList> matchList;
final ValueChanged finalBetList;
AppListView({this.onChange, this.matchList, this.finalBetList});
#override
_AppListViewState createState() => _AppListViewState();
}
class _AppListViewState extends State<AppListView> {
int counter = 0;
List<MatchList> betList = List<MatchList>();
I have a Home Screen that calls this Stateful Widget. In here I am using a callback function (onChange) to get the counter value from the widget. Everything is working perfect.
Stack(children: [
AppListView(
matchList: matchList,
//callback function brings the counter value from ListView class
onChange: (value) {
setState(() {
counter = value;
});
},
),
Positioned(
child: Padding(
padding: const EdgeInsets.only(top: 280.0, left: 330.0),
child: Container(
width: 60,
height: 60,
child: FloatingActionButton(
onPressed: () {
counter > 0
? Navigator.pushNamed(context, BetScreen.id)
: print('you shall not pass');
},
child: Text(
counter.toString(),
style: kTextStyleAppBarTitle,
),
),
),
),
)
])
What is the problem
But when I am trying to call that widget from another screen with a similar callback function(finalBetList), I got "The method 'call' was called on null. Receiver: null" error. Actually everything that I do is the same as the other example that works fine. I can't find what I'm missing. Is it something about Lists?
class _BetScreenState extends State<BetScreen> {
List<MatchList> betList = List<MatchList>();
int counter = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: CustomAppBar(
currentBalance: '\u0024' + '200.00',
userLevel: 'Level 30',
userName: 'Mark',
),
body: Container(
child: AppListView(
finalBetList: (value) {
setState(() {
//counter = value;
betList = value;
//print(betList);
});
},
matchList: betList,
),
),
);
}
}
After Edit
I added the cod of AppListView widget
class AppListView extends StatefulWidget {
final ValueChanged onChange;
final List<MatchList> matchList;
final ValueChanged finalBetList;
AppListView({this.onChange, this.matchList, this.finalBetList});
#override
_AppListViewState createState() => _AppListViewState();
}
class _AppListViewState extends State<AppListView> {
int counter = 0;
List<int> betList = List<int>();
#override
Widget build(BuildContext context) {
children: <Widget>[
AppButton.buildAppButton(
context,
AppButtonType.BETSELECTION,
widget.matchList[index].homeOdds,
kCategoryButtonDimensions,
color: widget.matchList[index].homeSelected
? Colors.yellow
: Colors.white,
onPressed: () {
if (widget.matchList[index].drawSelected ||
widget.matchList[index].awaySelected) {
widget.matchList[index].drawSelected =
false;
widget.matchList[index].awaySelected =
false;
counter--;
//betList part
if (betList.length > 0)
betList
.remove(widget.matchList[index].id);
}
widget.matchList[index].homeSelected =
!widget.matchList[index].homeSelected;
if (widget.matchList[index].homeSelected) {
counter++;
betList.add(widget.matchList[index].id);
} else {
counter--;
if (betList.length > 0)
betList.remove(widget.matchList[index]
.id); //if selected, add to betList to send BetScreen
}
widget.onChange(counter);
print(betList);
widget.finalBetList(betList);
setState(() {});
},
),

How to display element of list of string in many text field in flutter

i have a class that return a widget that create a text field i want call this class in a for loop in other class and every times that call this class text field set different value for my text fields and show text fields in list view how done it and next question about text field is when i set a value to text field and go to next page and back to my page value of text field cleaned and dont show how access to value of it this is my class that create a text field object
import 'package:art_man/components/Utility/Keys.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class InputText extends StatefulWidget {
String _hint, id;
double height;
TextEditingController ctrl;
Color brdercolor;
double brderwidth;
double radius;
double margintop;
TextAlign textAlign;
int maxlines;
Color hintconlor;
double hintsize;
double maxlenght;
TextInputType keyboardtype;
FontWeight fontWeight;
TextEditingController controller;
InputText(this._hint, this.id,
{this.height,
this.brdercolor,
this.brderwidth,
this.margintop,
this.radius,
this.textAlign,
this.maxlines,
this.hintconlor,
this.hintsize,
this.maxlenght,
this.keyboardtype,
this.fontWeight,
this.controller});
#override
myInputText createState() {
myInputText it = new myInputText(id,_hint,
height: height,
brdercolor: brdercolor,
brderwidth: brderwidth,
margintop: margintop,
radius: radius,
maxlines: maxlines,
hintconlor: hintconlor,
alignment: textAlign,
hintsize: hintsize,
maxlenght: maxlenght,
keyboardtype: keyboardtype,
fontwidth : fontWeight,
controller: controller);
return it;
}
}
class myInputText extends State<InputText> {
String _hint;
String id;
double height;
Color brdercolor;
double brderwidth;
double radius;
double margintop;
TextAlign alignment;
int maxlines;
Color hintconlor;
double hintsize;
double maxlenght;
TextInputType keyboardtype;
FontWeight fontwidth;
TextEditingController controller ;
myInputText(this.id,this._hint,
{this.height,
this.brdercolor,
this.brderwidth,
this.margintop,
this.radius,
this.alignment,
this.maxlines,
this.hintsize,
this.hintconlor,
this.maxlenght,
this.keyboardtype,
this.fontwidth,
this.controller});
#override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.center,
margin: EdgeInsets.only(
top: margintop == null ? 1.0 : margintop,
),
padding: EdgeInsets.only(right: 3),
height: height == null ? 40.0 : height,
decoration: BoxDecoration(
border: Border.all(
color: brdercolor == null ? Colors.white : brdercolor,
width: brderwidth == null ? 0.0 : brderwidth),
color: brdercolor == null ? Colors.white : brdercolor,
borderRadius: BorderRadius.circular(radius == null ? 25 : radius)),
child: TextField(
controller: controller,
keyboardType: keyboardtype==null?TextInputType.text:keyboardtype,
textInputAction: TextInputAction.next,
inputFormatters: [
new LengthLimitingTextInputFormatter(maxlenght==null?
30:maxlenght.toInt()),
],
onChanged: (value){
Kelid.setter(id, value);
print(Kelid.getter(id));
},
textAlign: alignment == null ? TextAlign.right : alignment,
maxLines: maxlines==null?1:maxlines,
style: TextStyle(
fontSize: hintsize==null?14:hintsize,
),
textDirection: TextDirection.rtl,
decoration: InputDecoration(
border: InputBorder.none,
contentPadding: const EdgeInsets.all(0.0),
hintText: _hint,
errorStyle: TextStyle(height: 0),
hintStyle: TextStyle(
fontWeight: fontwidth==null?FontWeight.normal:fontwidth,
color: hintconlor == null ? Colors.grey : hintconlor,
fontSize: hintsize == null ? 13 : hintsize)),
),
);
}
}
From what I understand, you're looking into generating TextField dynamically and be able to store its values. What you can do here is store TextField values using state management available in Flutter. In this approach, I've used the provider package to store the TextField values and are saved on every text change. With the TextField values stored, we can easily restore the values on the TextFields.
Complete sample
import 'dart:collection';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(
ChangeNotifierProvider(
create: (BuildContext context) => TextFieldDetailsModel(),
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: ListView.builder(
itemCount: 20,
itemBuilder: (BuildContext context, int index) {
return _inputText('Hint for index $index', index);
}),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
// Print all TextField String stored in Provider
// List<String> listText =
// Provider.of<TextFieldDetailsModel>(context, listen: false)
// ._textFieldContent;
// for (int x = 0; x < listText.length; x++) {
// debugPrint('Text: ${listText[x]}, index: $x');
// }
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondRoute()),
);
},
child: Icon(Icons.play_arrow),
),
);
}
Widget _inputText(String hint, int index) {
var _textEditingController = TextEditingController();
return Container(
padding: EdgeInsets.all(16.0),
child: Consumer<TextFieldDetailsModel>(builder: (BuildContext context,
TextFieldDetailsModel txtModel, Widget? child) {
// If Provider List contains value, prefill the TextField
if (txtModel._textFieldContent.length > 0 &&
txtModel._textFieldContent.length > index &&
txtModel._textFieldContent[index].trim() != '') {
_textEditingController.text = txtModel._textFieldContent[index];
// set TextField cursor at the end
_textEditingController.selection = TextSelection.collapsed(offset: _textEditingController.text.length);
}
return TextField(
controller: _textEditingController,
onChanged: (String str) {
// Only add String if TextField has value
if (str.trim().isEmpty)
txtModel.remove(index);
else
txtModel.add(str, index);
},
decoration: InputDecoration(hintText: hint),
);
}),
);
}
}
// https://flutter.dev/docs/development/data-and-backend/state-mgmt/simple
class TextFieldDetailsModel extends ChangeNotifier {
final List<String> _textFieldContent = [];
UnmodifiableListView<String> get text =>
UnmodifiableListView(_textFieldContent);
void add(String text, int? index) {
if (index != null)
_textFieldContent.fillAndSet(index, text);
else
_textFieldContent.add(text);
// This call tells the widgets that are listening to this model to rebuild.
notifyListeners();
}
void remove(int index) {
_textFieldContent.removeAt(index);
notifyListeners();
}
void removeAll() {
_textFieldContent.clear();
// This call tells the widgets that are listening to this model to rebuild.
notifyListeners();
}
}
// Create filler for skipping ranges
// Source: https://stackoverflow.com/a/65504227/2497859
extension ListFiller<T> on List<String> {
void fillAndSet(int index, String value) {
if (index >= this.length) {
this.addAll(List<String>.filled(index - this.length + 1, ''));
}
this[index] = value;
}
}
class SecondRoute extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Second Route"),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('Go back!'),
),
),
);
}
}
Demo
The values can also be stored only on page change. Create a List<TextEditingController> to be assigned on the TextFields generated.
late List<TextEditingController> _listTextEditingController = [];
...
Widget _inputText(String hint, int index) {
var _textEditingController = TextEditingController();
_listTextEditingController.add(_textEditingController);
...
}
On page change (i.e. button trigger), extract all the List values and store it in the Provider.
for (int x = 0; x < _listTextEditingController.length; x++) {
Provider.of<TextFieldDetailsModel>(context, listen: false).add(_listTextEditingController[x].text, x);
}

Flutter Dart: how can I change the value of a class property in a list of Classes

Hello I am having issue to understand how to manage properties of a class when it is part of a list of classes
Here are my classes
class BasketItem {
final String itemDescription;
final String itemNote;
BasketItem(
{this.itemDescription,
this.itemNote});
}
class AppData {
static final AppData _appData = new AppData._internal();
List<BasketItem> basketList = [];
factory AppData() {
return _appData;
}
AppData._internal();
}
final appData = AppData();
And here is my List
List<Container> _buildBasketList() {
return appData.basketList.map((bList) {
var container = Container(
child: Builder(
builder: (context) => Dismissible(
key: Key(UniqueKey().toString()),
background: Container(
margin: EdgeInsets.all(8.0),
color: kColorAccent,
child: Align(
alignment: Alignment(-0.90, 0.00),
child: Icon(Icons.add_comment)),
),
onDismissed: (direction) {
final newItemToAdd = BasketItem(
itemDescription: bList.itemDescription,
itemNote: 'xxxxx',);
appData.basketList.add(newItemToAdd);
setState(() {});
appData.basketList.remove(bList);
},
child: Stack(...)
),
),
);
return container;
}).toList();
}
I would like to do the following: when onDismissed get executed I would like to amend the property itemNote to 'xxxxx'. How can I do it? At the moment I remove the BasketItem I have swiped and I create a new BasketItem and I add it to the list. The problem is that this does not seem efficient and it also add the item at the end of the list while I would like to keep it at the same position/index where it was.
Thanks
Approach 1
Make fields in BasketItem non final. So you can amend them.
class BasketItem {
final String itemDescription;
/*final*/ String itemNote;
BasketItem(
{this.itemDescription,
this.itemNote});
}
// onDismissed will change itemNote.
....
onDismissed: (direction) {
setState(() {
bList.itemNote = 'xxxxx';
});
},
...
Approach 2
Replace list contents inline. Don't remove and add
List<Container> _buildBasketList() {
return appData.basketList.asMap().map((index, bList) {
var container = Container(
child: Builder(
builder: (context) => Dismissible(
key: Key(UniqueKey().toString()),
background: Container(
margin: EdgeInsets.all(8.0),
color: kColorAccent,
child: Align(
alignment: Alignment(-0.90, 0.00),
child: Icon(Icons.add_comment)),
),
onDismissed: (direction) {
setState(() {
appData.basketList[index] = BasketItem(
itemDescription: bList.itemDescription,
itemNote: 'xxxxx',);
});
},
child: Stack(...)
),
),
);
return container;
}).toList();
}