Listview builder not updating values in list - flutter

I have a positioned ListViewBuilder inside a Stack attempting to show a dynamic drop down across multiple tiles with different drop down values. This seems to work fine, except for the fact that the values don't seem to clear correctly when the list is updated with new values and refreshed on SetState.
ListView builder code
Positioned(
top: 100,
left: 100,
width: 90,
height: MediaQuery.of(context).size.height,
child: ListView.builder(
shrinkWrap: true,
scrollDirection: Axis.vertical,
itemCount: dropDownListValues.length,
itemBuilder: (context, index) {
return Tile(
tileId: dropDownListValues[index],
isHeaderTile: false,
uuid: Uuid().v1(),
);
},
),
),
Clear and update list based on which button is pressed.
void _toggleDropDown(List valueList) {
if (dropDownListValues.isEmpty) {
setState(() {
dropDownListValues = valueList;
});
} else {
setState(() {
dropDownListValues.clear();
});
}
}
What I end up getting is the list extends based on the number of items in the drop down, but the values from the previous list carry across from the last list..
Example.
Drop Down list values
Dropdown list 1 = ['one', 'two']
Dropdown list 2 = ['two', 'three', 'four']
What I am seeing is
Dropdown list 1 = ['one', 'two']
Dropdown list 2 = ['one', 'two', 'four']
The if I click on list two drop down first I get the following
Dropdown list 1 = ['two', 'three']
Dropdown list 2 = ['two', 'three', 'four']
Im stumped and have spend hours on this, any idea what I could be doing wrong to cause this refresh issue?
Thanks in advance.

Try using keys so the Flutter Framework can identify changes to your list.

Related

Flutter values in DropDownMenu

i was wondering if you use a DropDownMenu in Flutter there is a possibility to get different values.
For Example:
You have the items ['item1','item2','item3','item4','item5']
then you use a DropDownButton with these items. But for every single item there should be passed a different value so i can work with that later.
So that item1 = 90, item2 = 180, item3 = 270 and so on.
If item1 is selected it should be displayed to the user like that and the app gets the value 90 for example where it can work with later.
I hope you get what i mean and can help me a litte bit.
You can create a map and use it's key for DropDownMenu, While showing use key to show value like
String? dropdownValue;
Map<String, int> dataset = {
"item1": 90,
"item2": 180,
"item3": 270,
};
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
children: [
DropdownButtonHideUnderline(
child: DropdownButton<String>(
value: dropdownValue,
focusColor: Colors.white,
items: dataset.keys.map((String value) {
return DropdownMenuItem(
value: value,
child: Text(value),
);
}).toList(),
onChanged: (v) {
setState(() {
dropdownValue = v;
});
},
),
),
Text(
dropdownValue == null
? "Select Item "
: dataset[dropdownValue!].toString(),
),
There are a lot of ways to do so.
1- Through key-value pairs.
Get your sample for example:
items[{'name':'item1','value':'90'},{'name':'item2','value':'180'},{'name':'item3','value':'270'},{'name':'item4','value':'360'}];
(presumably they are a 360 degree angels :-))
Then you can point to each item like this: items[i]['name'] or items[j]['value'] and so on...
2- Not preferred way: two different lists like this:
itemNames[items1,item2,item3, item4];
itemValues [90,180,270,360];
Then you can pass the itemNames list to your dropdownMenu and whenever an item has been selected you can find it's index and point to the itemsValues' appropriate row. For instance: dropdownMenu item3 selected so the returned index should be 2 (indexes are starting from 0) so you can find the desired value like this: desiredValue = itemsValue[2]; (270)

Can I Change the data of a single particular item of a Listview.builder, Without losing the previous data for the other items In Flutter

Let me Explain,
Suppose I have a Listview.builder with 3 item
and I Want to change the data of item no.2 without changing the other items data(1 and 3).
And I have two data sources for updating on those 3 items
according to button press.
Please Help me if you have any idea to solve this problem.
Thank You
Welcome #Rahul Choudhury!
ListView.builder has the property itemBuilder who accepts the index argument.
You can use that one!
final items = List<String>.generate(3, (i) => "Item $i");
const int specialIndex = 1;
return ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
if (specialIndex == index){
//Use here your custom amazing widget
return const ListTile(
title: Text("Flutter is awesome"),
);
}else {
return ListTile(
title: Text(items[index]),
);
}
},
);
Obviously, I suggest you to refactor this code as you like, but I wanted to give you an idea ;)

I am trying to get a key and and a corresponding value from a Dart map but I'm failing no matter what I try

Here is the data; it's a list and one of the items (credit vendors) is a map of k, v pairs. I want to put each key and each value in their own Text widget. The code is below.
List<Blog> blogs = [
Blog(
title:
"She changed religion to marry the man of her dreams- Hamidah and Efraim’s classy Kwanjula ",
description:
"Right after their arrival, Efraim and a select few of his entourage went right into "
"Hamidah’s parents’ house for the basic session of kwanula: to be accepted and born into this home. \n",
author: "James Arthur",
date: "09 Jan 20",
views: 3899,
comments: 23,
imagePath: "assets/images/intro.jpg",
creditVendors: [
{'Decorator': 'Parties & Events'},
{'Photography': 'Paramount Images'},
{'Food': 'Spectrum Restaurant'},
{'Juice': 'Dalausi Juice'},
{'Bridal': 'Sheena Collections'},
],
),
Blog(),
Blog(),
];
So I want to call creditVendors values within another class on a different page. Below is the code to illustrate what I am trying to achieve.
Container(
height: 200.0,
child: ListView.builder(
itemCount: widget.blog.creditVendors.length,
itemBuilder: (BuildContext, int index) {
Blog blog = blogs[index];
return Row(
children: [
Text(blog.creditVendors.keys),
Text(blog.creditVendors.value),
],
);
}),
)
You can iterate over maps by their values or keys. That won't be necessary since your creditVendors is an array itself.
Then you can use ListView widget to create widgets of your own choice from the values of the map.
Sample code
List<dynamic> creditVendors = [
{'Decorator': 'Parties & Events'},
{'Photography': 'Paramount Images'},
{'Food': 'Spectrum Restaurant'},
{'Juice': 'Dalausi Juice'},
{'Bridal': 'Sheena Collections'},
];
ListView.builder(
itemCount: creditVendors.length,
itemBuilder: (ctx, int index) {
return ListTile(
*emphasized text* title: Text(
'${creditVendors[index].keys.first} - ${creditVendors[index].values.first}'));
}),
You can get all the keys from a map using mapName.keys method
You can get all the values from a map using mapName.values method
Update:
You have to use nested ListView since you iterating over two lists -> one is blogs array and inside that you want to iterate over that blog's creditVendors list.
Please see the demo code:
ListView.builder(
itemCount: widget.blog.length,
shrinkWrap: true,
itemBuilder: (ctx, int _blogIndex) {
Blog_blog = blogs[_blogIndex];
return ListView.builder(
itemCount: _blog['creditVendors'].length,
shrinkWrap: true,
itemBuilder: (ctx, int index) {
return ListTile(
title: Text(
'${_blog['creditVendors'][index].keys.first} - ${_blog['creditVendors'][index].values.first}'));
});
}))
I hope you know that the blog list has only one elemet of type Blog
So I did it for only one element. If you have more just iterate rest everything will be same.
You should do the following:
Blog a = blog[0]; //this will put the first element in a
then:
define variables as maps because the content inside creditVendors is of type maps
and creditVendors is again a list data type.
// this is just to collect the data inside creditVendors.
Map decorator;
Map photography;
Map food;
Map juice;
Map bridal;
decorator = a.creditVendors[0];
photography = a.creditVendors[1];
food = a.creditVendors[2];
juice = a.creditVendors[3];
bridal = a.creditVendors[4];
Now:
you can access each map data as:
Text(decorator['Decorator'],)
Text(photography['Photography'],)
Text(food['Food'],)
Text(juice['Juice'],)
Text(bridal['Bridal'],)
PS. CreditVendor is a list of Maps

How to add items to a Row object at runtime in Flutter?

The most amazing part of Flutter is that every widget is an object. But, when I try to make an object from a Row and add a List of widgets to its children, I get this error on runtime:
Cannot add to an unmodifiable list
I'm currently creating my Row like this:
Row rowStar = Row();
rowStar.children.addAll(rowChildren);
Is this wrong? If so, what's the correct way?
1. Why it won't work
If all you want to do is add a list to a Row, you should create the list and then initialize the Row with it. You cannot add the list afterwards because Flutter's widgets typically have final fields/properties, which are not modifiable at runtime.
2. The Flutter Way
However, what you could do to dynamically modify the list is pass a function that builds your list internally at runtime. And that's exactly what the ListView.builder() constructor of ListView does.
For example, this the docs example for dynamically creating a List of Containers based on the the Lists entries and colorCodes.
final List<String> entries = <String>['A', 'B', 'C'];
final List<int> colorCodes = <int>[600, 500, 100];
ListView.builder(
padding: const EdgeInsets.all(8),
itemCount: entries.length,
itemBuilder: (BuildContext context, int index) {
return Container(
height: 50,
color: Colors.amber[colorCodes[index]],
child: Center(child: Text('Entry ${entries[index]}')),
);
}
);
You could add a button to .add, say, the letter D and the colorCode 400, to the respective Lists, and Flutter would render the respective Containers dynamically on the screen.
I'm not sure if I get it right, but what you're trying to do doesn't make sense. If you want to reuse a Row with widgets and information you can just build it once and save the whole thing as widget.
Try it like this:
Build a Row.
Create widgets for your children.
Display them as widgets of the row.
Row(
children: <Widget>[
customWidget1(),
customWidget2(),
customWidget3(),
],
)
Your custom widgets are the content of the Row then.
Hope it helps.
In my case, I can make a Row from a previously created List<Widget>:
Row calculateListOfStars(ranking) {
final List<Widget> rowList = [];
rowList.addAll([
// make my custom widget
]);
final Row rowStars = Row(
children: rowList,
);
return rowStars;
}
Use Listview:
ListView(
children: rowStar((data) {
return ListTile();
}).toList();

Flutter Animated List: Conditionally add ListView item

I am have an animated list in my flutter project.
For every element in that list I have a grid of buttons that are placed dynamically from a Firestore stream. Sometimes that will come back with 10 items in the grid but other times that will comeback with 0 items.
When a button on the grid in a list element is pushed it will search firestore and create a new grid of buttons in the next list element below.
The problem that I have is when it comes back with 0 grid buttons I don't want it to create a new list element (an empty list element with no grid buttons). I tried returning a container with 0 size as a work around but animated list still gives it some height so you can see there is a problem. I also understand that this would be bad practice as you have non visible empty list elements in the list.
I start with a list of foods as strings:
List foodListGrids = ['breads','drinks']
I then have an animated list:
AnimatedList(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
key: _FoodandDrinkKey,
initialItemCount: foodListGrids.length,
itemBuilder: (context, index, animation) {
return SizeTransition(
sizeFactor: animation,
child: buildButtonGridItemsMenu(index),
);
},
),
I set the AnimatedList size to the length of the foods list.
I set the child of the Animated List to a class that searches firebase and returns a card with the grid of buttons on it like this:
StreamBuilder(
stream: Firestore.instance
.collection(widget.categoryType)
.where(widget.relationship, isEqualTo: widget.searchString)
.snapshots(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (!snapshot.hasData) {
return Container(width: 0, height: 0,);
} else if (snapshot.hasData) {
List<Widget> widgetList = [];
List<DocumentSnapshot> documentList = snapshot.data.documents;
if (documentList.length > 0) {
for (int i = 0; i < documentList.length; i++) {
widgetList.add(ButtonTheme(
minWidth: 16,
height: 30,
child: GridButton(snapshot, i, widget.listIndex),
));
}
return Container(
width: double.infinity,
child: Wrap(
children: widgetList,
alignment: WrapAlignment.center,
));
} else{
return Text('NO DATA BECAUSE NUMBER OF GRID ITEMS IS 0');
}
} else {
return Text('NO DATA BECAUSE GRID ITEMS CALL IS NULL');
}
},
),
then in the on pressed method for each grid button I add a new list element like this:
void _insertCategoryGridItem(String id, int index) {
if (!foodListGrids.contains(id)) {
foodListGrids.add(id);
_FoodandDrinkKey.currentState.insertItem(index + 1);
}
}
The problem is a chicken or the egg problem I will try to show below:
List item is generated from the index 0 in the food list and all stream data is if from food list index 0 firebase results.
On pressed for a grid item in the first list row is pressed to add a new list row with a new set of grid items. This will then update the food list array and the call for the list to add new row of grid buttons. The issue is because this is in the onpressed for the first rows grid there is no knowledge of what will be returned for the next row so there is no way of knowing if it will return a grid of size 0 in the next list row in the current way it is setup.
I have tried returning null, container of 0 width and height but have had no luck. I am not sure what I can do to fix it.
Thanks for your help
I'm not sure if I get you right but seems that I faced the same problem with AnimatedList and stream of data from the Firestore. The problem is in initialItemCount: property of the AnimatedList.
In my case I wanted to change AnimtedList in two ways:
I wanted to manually add an item and to show it with animation.
I want that if the list is changed due to a new portion of data from the stream - I want the list to be updated without animation of inserting and without errors (out of range).
To solve this I did a dirty hack: when there is an update from the stream I reinit the key of the list, in your case it's _FoodandDrinkKey. So BEFORE you build the AnmatedList just reinit your key _listKeyUserNotes = GlobalKey(); that's how the List will "forget" about it's initialItemCount and will render a new data without any out-of-range errors.
When you want to add a new item manually with animation - use insert().
key: _FoodandDrinkKey,
initialItemCount: foodListGrids.length,
Hope this makes sense.