I am trying to create a list of reorderable items with with Reorderables package; I have a reorderable wrap, inside it I have reorderable columns. I'm using two diffirent list for this.
List<Widget> items = [];
List childrenItems = [];
The lists are created from data looking like this
List taskList = [
// First child Item
{
"name":"NOrbert kRoSs",
"sub-children":[
"child1","child2"
],
},
// Second child Item
{
"name":"Genius",
"sub-children":[
"Relerx","Yiuja","No filter"
],
},
];
The Method used to populate the lists
generateList(List incoming){
List<Widget> children = [];
children.clear();
if(childrenItems.length > 0) {childrenItems.clear();}
if(items != null) items.clear();
if( incoming != null )
for(int n=0; n<=incoming.length-1;n++){
children.clear();
for(int sub = 0; sub<=incoming[n]["sub-children"].length-1;sub++){
children.add(
cardWidget(
header: incoming[n]["sub-children"][sub]["name"].toString(),
key: sub.toString(),
parentKey: incoming != null?n.toString():"0",
),
);
}
childrenItems.insert(n, children);
items.add(
mainListItem(
nPos: n,
header:incoming !=null?incoming[n]["name"].toString():"Aberor",
mainKeys: n.toString(),
),
);
}
}
The reorderables
They
ReorderableWrap(
children: items != null?
items:
(<Widget>[
Container(
key: ValueKey("newVal"),),
]),
onReorder: setRedorder),
ReorderableColumn(
children: childrenItems[nPos] != null?
childrenItems[nPos]:
(<Widget>[
Container(
height: 30,
color: Colors.redAccent,
width: 60.0,
key: ValueKey("value"),
),
]),
onReorder: setNewOrder,
),
The Problem
When I run the app I get the error below
Duplicate GlobalKeys detected in widget tree.
The following GlobalKeys were specified multiple times in the widget tree. This will lead to parts of the widget tree being truncated unexpectedly, because the second time a key is seen, the previous instance is moved to the new location. The keys were:
[GlobalObjectKey ValueKey<String>#c9f0c]
[GlobalObjectKey ValueKey<String>#7990e]
[GlobalObjectKey ValueKey<String>#89542]
This was determined by noticing that after widgets with the above global keys were moved out of their respective previous parents, those previous parents never updated during this frame, meaning that they either did not update at all or updated before the widgets were moved, in either case implying that they still think that they should have a child with those global keys.
The specific parents that did not update after having one or more children forcibly removed due to GlobalKey reparenting are:
Column(direction: vertical, mainAxisAlignment: start, mainAxisSize: min, crossAxisAlignment: center, renderObject: RenderFlex#1c68d relayoutBoundary=up36)
I am a 100% sure there are no duplicate keys and that from the error message its the parents that are not updating. How can I fix this
For some reason I do not understand, doing
children = [];
Instead of
children.clear();
Solves my issue.
Try to use the different ValueKey.
The description in the key.dart said that LocalKey(ValueKey extended LocalKey) must be unique amongst the Element with the same parent.
Related
I am building a fitness app (image here: Fitness App example
) where the user can log their sets. I am having an issue when using the dismissible widget inside of my app. The swipe to delete functionality sends the following exception: a dismissed dismissible widget is still part of the tree
When swiping to delete a single set, I still need to retain the information the user has put into the other sets. I believe this is an issue with the key, however I've already tried UniqueKey() (which resets all of the other input fields) and the example below.
How can I remove a single set using dismissible and still retain the rest of the users data for the other sets? Thanks.
late List count = [0];
ListView.builder(
shrinkWrap: true,
itemCount: _count.length,
itemBuilder: (context, index) {
// Create a new variable to display the set
int setNumber = index + 1;
return Dismissible(
key: ValueKey(_count[index]),
background: _swipeStyle(),
onDismissed: (direction) {
// Remove the item from the data source.
setState(() {
_count.removeAt(index);
});
},
child: Row(
children: [
Expanded(flex: 1, child: Text('Set $setNumber')),
Expanded(flex: 2, child: _buildWeight(index)),
const SizedBox(
width: 24.0,
),
Expanded(flex: 2, child: _buildReps(index)),
],
),
);
},
),
Since the Key is based on a list of ints, maybe there are repeated keys? In that case the framework won't know which item was removed and will trigger the error you just found.
A possible solution would be to assign an unique ID to each item, that way you will never have repeated keys.
Try replace key: ValueKey(_count[index]) with UniqueKey()
I have a list of items (5-6 items) that are displayed using a ListView.builder. Each item contains a DropdownButton widget for the user to pick a number from 1 - 1000, thus containing 1000 DropdownMenuItems.
I implemented it as shown below, but the problem is that scrolling down the ListView is too slow and stutters. Even if the listView has 5 or 6 items, but note that each of them has an embedded DropdownButton containing 1000 DropdownMenuItems.
Is there a fix? Or another way to achieve my requirement?
N.B: Even if I reduce the number of DropdownMenuItems to 100, it still stutters when scrolling down the ListView.
class List extends StatelessWidget {
final List<Item> // Contains 5 items.
final List<int> quantityList = List<int>.generate(1000, (int i) => i);
//--
child: ListView.builder(
itemBuilder: (buildContext, i) {
return MyItem(
quantityList,
);
},
itemCount: items.length(),
)
class MyItem extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Container(
child: DropdownButton<int>(
items: quantityList
.map(
(int e) =>
DropdownMenuItem<int>(
value: e,
child: Text(e.toString()),
),
)
.toList(),
),
),
);
}
Edit
I changed MyItem class to be as below, but still, the same problem exists.
Tried using ListView and ListView.custom instead of ListView.builder, to build the whole list in advance instead of lazily according to this, but still same issue.
I also tried running the app using --profile configuration to simulate a release version. The performance is better but still suffers from terrible stuttering and lag. Tested on emulator and physical device.
class MyItem extends StatelessWidget {
List<DropDownMenuItem> quantityList; // contains 1k
#override
Widget build(BuildContext context) {
return Container(
width:300,
height:300,
child: DropdownButton<int>(
items: quantityList,
),
),
);
}
ListView will create and destroy widgets as they come in and out of view. Your MyItem widget is a very heavy widget (lots of stuff to create and destroy).
You probably don't want each MyItem to be a Scaffold(). Normally you only have 1 Scaffold() visible as it's a fancy root view for an app. It has an app bar, snack bar, drawer, etc. Try having just your Container(...) that's currently under body: as the return from your MyItem.build().
In the items: of your DropdownButton, you build and destroy the list of items when the DropdownButton scrolls in and out of view. If this list is the same for every widget in your list like in your test code above, create one List<Widget>[] that contains your DropdownMenuItem widgets and pass it in to your MyItem() widgets. Something like:
//In your widget with the ListView
List<Widget> myItems;
//In the initState() of your widget with the ListView
...
myItems = quantitySelection.map(
(int e) => DropdownMenuItem<int>(
value: e,
child: Text(e.toString()),
),
).toList(),
...
//In your ListView.builder()
return MyItem(
...
items: myItems,
...
);
//In your MyItem.build() -> DropdownButton()
...
DropDownButton(
items: items
),
...
FWIW - we have a ListView with complex children that we test with 10,000 items. There's a significant difference in performance between the debug and release builds in Flutter. It stutters a little in the debug builds but is very smooth in the release builds.
I was able to solve the issue by only using the cacheExtent property of ListView.builder, setting it to list.length*200. This is kind of a workaround knowing that in my case the list length will always be small.
Pre-building the DropDownMenuItems had no sensed performance enhancement by a user, but it is a good recommended practice regardless, as instead of building the same DropDownMenuItems over and over again for every list item.
Although according to the docs: ListView and ListView.separated does not lazy load items rather build them all up at the beginning, I kept experiencing the same stuttering and lag during scrolling as with ListView.builder.
How can I move an Positioned Widget in Flutter that was created at runtime?
Szenario:
Lets say I have a Stack Widget. The children are a variable of type List<Widget>[].
I receive the command to create a new Positioned Widget.
A second stream sends updated x and y positions, those I want to apply on left and top of the dynamically created Positioned Widget.
To do this I added two variables to my class and set them to left and top of the Positioned Widget.
While this works as along as the Positioned Widget is created during build time, it does not work when I dynamically add the Widget to the List<Widget> at runtime. (the Positioned Widget appears, but i can not change left/top through the variables of _MyClass)
Some pseudo code that shows the bits.
class _MyClass extends State<StatefulWidget> {
double x = 0.0;
double y = 0.0;
List<Widget> myList = [];
[...]
void _updateLocation(dx, dy) { // called by a stream...
setState(() {
x = dx;
y = dy;
});
}
[...]
void addHeart() {
myList.clear();
myList.add (
Positioned(
top: y,
left: x,
child:Icon(
Icons.favorite,
color: Colors.pink,
size: 24.0,
),
)
);
}
[...]
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Demo"),
),
body: Center(
child: Container (
child: Stack(
children: myList, //this does not update the top/left of the object
/* //THIS WORKS
children: <Widget>[
Positioned(
top: y,
left: x,
child:Icon(
Icons.favorite,
color: Colors.pink,
size: 24.0,
),
)
]
*/
)
)
),
);
}
I'm new to Flutter, and I propably don't get the mechanics right here, though, I tried to get this working with setState and Keys, but I feel like I'm missing a important bit of information here.
A possible hack I found is to provide the List<Widget> through a function to the Stack Widget and rebuilding the List<Widget> everytime I get an update on x and y. Which essentially deletes and adds the Widget from the stack everytime I get a new x and y... This seems like the worst way to achieve what I want.
How can I do this in a better way, what I'm missing here, why can I update top/left through x and y when creating the widget in build(..) but not when adding it to the list at runtime?
Ofcourse it would not work when you have a predefined list as children of stack. For your widget to move and change its location, flutter needs to actually rebuild the changed widgets, and note that it does call build method each time it re-renders.
So it is not a bad solution to call a function which creates the list each time (as it is exactly what flutter does for all widgets).
You do not need to worry about adding and deleting the items of stack each time the function is called, that is because flutter does not actually re-render anything on the page if it has not changed compared to current content of the page.
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();
Column(
children: <Widget>[
...myObject
.map((data) => Text("Text 1"), Text("Text 2")),
]
);
This block of code will fail because I'm returning 2 widgets instead of one. How could I fix it and return as many widget as I want without creating another column inside the map?
First you cant use an arrow function to return multiple values, you need to change it to a normal function that returns a list of widgets. Second, you need to use the .toList() method since .map is lazy and you need to iterate in order to map execute.
With this 2 steps you are going to end with a List<List<Widget>> and you should flat it before return it to a column that needs a List<Widget>. This can be achieved with the Iterable.expand() with an identity function.
You can try something like this:
Column(
children: <Widget>[
..._generateChildrens(myObjects),
],
),
And the actual implementation to obtain the widgets is:
List<Widget> _generateChildrens(List myObjects) {
var list = myObjects.map<List<Widget>>(
(data) {
var widgetList = <Widget>[];
widgetList.add(Text("Text 1"));
widgetList.add(Text("Text 2"));
return widgetList;
},
).toList();
var flat = list.expand((i) => i).toList();
return flat;
}
Hope it helps!