Flutter: Multiple widgets used the same GlobalKey - flutter

When I run this code I'm getting an error: Multiple widgets used the same GlobalKey.
So I can fix this issue.
How I can pass dynamically keys to my listview.Builder. is it possible to pass?. Here is simplified version my code:
GlobalKey<AutoCompleteTextFieldState<String>> key0 = new GlobalKey();
#override
Widget build(BuildContext context) {
return M Scaffold(
appBar: appBar,
body: SingleChildScrollView(
child: Container(
child: ListView.builder(
itemCount: 3,
itemBuilder: (context, index) {
return SimpleAutoCompleteTextField(
key: key0,
suggestions: suggestions,
textChanged: (text) => print(text),
clearOnSubmit: false,
textSubmitted: (text) => print(text)
),
}
),
),
),
);
}

Pass index as your value
key: ValueKey(index),

GlobalKey must be unique across the entire application.
You cannot simultaneously include two widgets in the tree with the same global key. Attempting to do so will assert at runtime.
It means that multiple elements can't use the same key. Here's a short video about keys in Flutter and not that short article about using keys, including GlobalKey's.
What you should do in this case is to derive keys of each element from their uniquely defining features such as content or some ID.
Things to note:
1.
Key('AAA') == Key('AAA'); // true
it means that you don't have to create Key objects outside of your build function and can go with just:
return SimpleAutoCompleteTextField(
key: Key(itemId),
or even rather ValueKey(itemData) in your case: ValueKey.
GlobalKey's are relatively expensive and if you don't explicitly need to move them from one place of the app to another, prefer regular Key.
Don't derive keys from indices. It's considered a bad practice because it will lead to misbehavior when you try to reorder existing widgets in the same subtree.

Related

Button built from ListView.builder seems to call the same function for all buttons built from the ListView

Basically, I have two nested ListView. Builders inside a third ListView builder. The two nested listview builders build two rows of custom buttons; the contents of the second row of buttons depend on the selected button in the two rows. The first row works entirely as intended, but the second row causes an issue.
Whenever a button from the second row is pressed, it affects the state of all the other buttons (and the data those lists are built on), as if calling the same function on all elements on the list. The strangest thing, though, is that it only seems to call PART of that function:
void toggleSubcategoryPanelButton(int panelNum, int subCategoryIndex) {
App()
.panelNames[panelNum]
.panelSettings
.categories[
App().panelNames[panelNum].panelSettings.activeCategoryIndex]
.activeSubcategory =
App()
.panelNames[panelNum]
.panelSettings
.categories[
App().panelNames[panelNum].panelSettings.activeCategoryIndex]
.subcategories[subCategoryIndex];
rerollName(panelNum);
saveSettingstoPrefs();
notifyListeners();
}
App() is a singleton that holds pretty much all the data for this lightweight app. The purpose of this function is to set a new index for activeSubcategory for only the 'panel' from which the function was called (given via panelNum). Instead, the function sets activeSubcategory of to the same index for all of the elements of panelNames in their categories at the current activeCategoryIndex.
Then, rerollName() seems to only be called for the panelNum provided.
The problem can be seen here. The top row's selection is set correctly.
[![Different function][1]][1]
You can (hopefully) see in this gif that the application correctly identifies the panelNum as it is displayed in the terminal (index of 1).
The ListView in question:
SizedBox(
height: 30.0,
child: ListView.builder(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
physics:
const NeverScrollableScrollPhysics(),
itemCount: widget
.panelSettings
.categories[widget.panelSettings
.activeCategoryIndex]
.subcategories
.length,
itemBuilder: ((context, index) {
return Consumer(
builder: (context, value, child) {
return PanelButtonToggleable(
icon: widget
.panelSettings
.categories[widget
.panelSettings
.activeCategoryIndex]
.subcategories[index]
.icon,
tooltip: widget
.panelSettings
.categories[widget
.panelSettings
.activeCategoryIndex]
.subcategories[index]
.getName(),
buttonBehavior: () =>
_onToggleSubcategoryClick(
index),
toggled: widget
.panelSettings
.categories[widget
.panelSettings
.activeCategoryIndex]
.subcategories[index]
.getName() ==
widget
.panelSettings
.categories[widget
.panelSettings
.activeCategoryIndex]
.activeSubcategory
.getName(),
);
},
);
}),
),
),
https://github.com/trevclaridge/Name-Generator-Extension
[1]: https://i.stack.imgur.com/Z2tg8.gif
I discovered that my issue actually had nothing to do with the ListViews at all. Rather the objects pointed to by widget.panelSettings.categories were actually the same instance of a List. Thus, assigning the entry for activeSubcategory was assigning it for all the objects that pointed to the categories list.
Confirm this in your own projects using Dart's identical method. Link here.
To solve this, I created a new class Categories with an empty constructor to hold the list, so every initialization of the list was from a new object.

Generic filter Flutter

Goodmorning,
I'm developing an app with flutter but I'm facing some problems with Provider (I think something miss in my knowledge).
My app fetch data from my API and displays them in listview.
In whole app I have different screen which displays different data type in listview and now I want to create filtering logic.
To avoid rewrite same code multiple times I thought to create one screen to reuse for filtering purposes but I'm facing problems with state management.
What I did:
create base model for filter information
`
enum FilterWidget { TEXT_FIELD, DROPDOWN } //to resolve necessary Widget with getWidget() (to implement)
class FilterBaseModel with ChangeNotifier {
String? value= 'Hello';
FilterWidget? widgetType;
FilterBaseModel(this.value, this.widgetType);
onChange() {
value= value== 'Hello' ? 'HelloChange' : 'Hello';
notifyListeners();
}
}
`
One screen for display filters depending on request
List<FilterBaseModel> filters = [];
FilterScreen() {
//Provided from caller. Now here for test purposes
filters.add(FilterBaseModel('Filter1', FilterWidget.TEXT_FIELD));
filters.add(FilterBaseModel('Filter2', FilterWidget.TEXT_FIELD));
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: SafeArea(
minimum: EdgeInsets.symmetric(vertical: 15, horizontal: 15),
child: SingleChildScrollView(
child: Container(
height: 400,
child: Column(
children: filters
.map(
(e) => Consumer<FilterBaseModel>(
builder: (_, filter, child) =>
ChangeNotifierProvider.value(
value: filter,
child: CustomTextField(
`your text` initialText: e.value,
onTap: () {
e.onChange();
filter.onChange();
},
),
),
),
)
.toList(),
))),
),
);
}
`
The problem is in Consumer and ChangeNotifier.value.
Screen works quite well: widget are displayed and callback are called, what is wrong? I need to use onChange method of both instance to update UI otherwhise method was called but widget is not rebuilt.
I know that probably putting consumer there is not right way but I tried also to put outside but doesn't work.
I expect to have one filter screen which receives in input filters list information, display them, handle their state managment and return their value.
P.S: this code now works, but I know is not the right way
Thank you for help!
EDIT
Have same behaviour without ChangeNotifierProvider.value. Therefore I'm more confused than before because still persist the double call to onChange for correct rebuilding.
More bit confused about ChangeNotifierProvider.value using...

Flutter: a dismissed dismissible widget is still part of the tree

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()

Flutter: scrolling a list of DropdownButtons each containing 1k selections is too slow

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.

Dynamic ListView of stateful widgets not working

Dears,
it could be just a mistake of mine but ...it's driving me crazy ;)
I have a ListView inside my statefulwidget:
Expanded(
child: ListView.builder(
padding: EdgeInsets.all(10.0),
itemCount: searchableFoodList.length,
itemBuilder: (BuildContext context, int index) {
print('4 - SFL - ${searchableFoodList[index].id} - ${searchableFoodList[index].name}');
return
FoodItem(
id: searchableFoodList[index].id,
baseUnit: searchableFoodList[index].baseUnit,
baseUnitS: searchableFoodList[index].baseUnitS,
baseVal: searchableFoodList[index].baseVal,
baseValCal: searchableFoodList[index].baseValCal,
mediumVal: searchableFoodList[index].mediumVal,
mediumValCal: searchableFoodList[index].mediumValCal,
name: searchableFoodList[index].name,
note: searchableFoodList[index].note
);
},
),
searchableFoodList is modified (according user selection) inside a setState().
Let say that we have a 3 FoodItem(s), so my searchableFoodList is [FoodItem-1, FoodItem-2, FoodItem-3] and everything is working fine.
If I select a single item in my list, let say "FoodItem-2", the searchableFoodList becomes [FoodItem-2] and the list displayed contains (correctly) just one item but it is FoodItem-1.
Note that I inserted a print ...and it prints "FoodItem-2"
I guess that the problem is that it is considered the "original list" and it is changed only the length of the list but the list itself is not re-generated.
I have no more ideas ...any suggestions to solve this problem?
P.S.
FoodItem is stateful widget as well (an animated one). I did something similar with using stateless widget and I didn't have any problem