Open Suresh-Alangat opened 3 years ago
I'm having the same problem, is pretty dissapointing that I can't update the value for the SearchableDropdown. It works for me when I add an auxiliary variable _city
that holds the id for the value of the SearchableDropdown, in the best case scenario I should be able to set user.userData.city
as my value.
int _city;
Consumer<City>(
builder: (ctx, cityData, child) =>
SearchableDropdown(
items: cityData.cities
.map(
(city) => DropdownMenuItem(
value: city.id,
child: Text(city.name),
),
)
.toList(),
value: _city,
onChanged: !_status
? (newValue) => setState(
() {
_city = newValue;
user.userData.city = _city;
},
)
: null,
hint: const Text("Ciudad"),
),
);
That's how I made it work, but the searchableDropdown, searches by value and not child, my values are numbers and the child is the description, there's no point of searching cities by their id (1, 2, 3...) I added searchFn, to search by child data, and got the same error again.
RangeError (index): Invalid value: Only valid value is 0: 2
this is my code with the searchFn added, I don't understand how that function broke it
Consumer<City>(
builder: (ctx, cityData, child) =>
SearchableDropdown(
items: cityData.cities
.map(
(city) => DropdownMenuItem(
value: city.id,
child: Text(city.name),
),
)
.toList(),
value: _city,
onChanged: !_status
? (newValue) => setState(
() {
_city = newValue;
user.userData.city = _city;
},
)
: null,
hint: const Text("Ciudad"),
searchFn: (String keyword, items) {
List<int> ret = [];
if (items != null && keyword.isNotEmpty) {
keyword.split(" ").forEach((k) {
int i = 0;
items.forEach((item) {
if (k.isNotEmpty &&
(item.child.data
.toString()
.toLowerCase()
.contains(k.toLowerCase()))) {
ret.add(i);
}
i++;
});
});
}
if (keyword.isEmpty) {
ret = Iterable<int>.generate(items.length)
.toList();
}
return (ret);
},
),
);
Hello @Suresh-Alangat and @JHeisecke ,
Here is some similar code based on the "Single dialog object" example which works fine at least with the search_choices
plugin:
SearchChoices.single(
value: selectedNumber,
items: numberItemsEmpty ?? [],
label: numberItemsEmpty == null
? ElevatedButton(
onPressed: () {
setState(() {
numberItemsEmpty = ExampleNumber.list.map((exNum) {
return (DropdownMenuItem(
child: Text(exNum.numberString), value: exNum));
}).toList();
});
},
child: Text("Generate items list"))
: ElevatedButton(
onPressed: () {
setState(() {
selectedNumber = null;
numberItemsEmpty = null;
});
},
child: Text("Reset items list")),
hint: (numberItemsEmpty?.isEmpty ?? true) ? "No items" : "Select Item",
onChanged: (value) {
setState(() {
selectedNumber = value;
});
},
isExpanded: true,
)
As you can see, there is a buton to generate the list and another one to clear it. I get no error using both of them.
Can you please let me know how I should adapt this example to reproduce your issue?
Should I try to reset the list of items from the dialog? This could maybe explain the error you are getting.
Hi @lcuis thanks for the response. I'm trying to understand your comment but I can't seem to, sorry, I'll try to explain what I'm doing so you can reproduce my error.
My code with minor tweaks
Consumer<City>(
builder: (ctx, cityData, child) =>
SearchableDropdown.single(
items: cityData.cities
.map(
(city) => DropdownMenuItem(
value: city.id,
child: Text(city.name),
),
)
.toList(),
value: user.userData.city,
onChanged: !_status
? (newValue) => setState(
() {
user.userData.city= newValue;
},
)
: null,
hint: const Text("Ciudad"),
searchFn: (String keyword, items) {
List<int> ret = [];
if (items != null && keyword.isNotEmpty) {
keyword.split(" ").forEach((k) {
int i = 0;
items.forEach((item) {
if (k.isNotEmpty &&
(item.child.data
.toString()
.toLowerCase()
.contains(k.toLowerCase()))) {
ret.add(i);
}
i++;
});
});
}
if (keyword.isEmpty) {
ret = Iterable<int>.generate(items.length)
.toList();
}
return (ret);
},
isExpanded: true,
),
);
I get my list from a petition I do to my backend and map it to a List of objects, for example:
[{id: 0, name: Asuncion}, {id: 1, name: San Lorenzo}, {id:2, name: "Lambare"}]
(in my code that would be the value of cityData.cities
) like so. For the purpose of this issue, let's assume cityData.cities
is never null or empty.
Images for reference:
user.userData.city
holds the id of the city picked its value is changed on the onChanged function. Also user.userData.city
is nevel null, it always has a value. user.userData.city
is the value I want to send to my backend.
When I pick a city other than "Asuncion" (id 0) and save it. I get the error RangeError (index): Invalid value: Only valid value is 0: X
X being the id of the city picked (always other than Asuncion). Another error is, when I pick Asuncion as my city and save it, I can't change it back to another city, when debugging newValue
is receiving always value 0 (which is Asuncion) and not 1 or 2 when they're picked.
I already had this working on a DropdownButtonFormField
but I need a searchBar because in the future I should have more tan 200 cities in that dropdown. This is how it works on that widget
Consumer<City>(
builder: (ctx, cityData, child) =>
DropdownButtonFormField(
items: cityData.cities
.map(
(city) => DropdownMenuItem(
value: city.id,
child: Text(city.name),
),
)
.toList(),
value: user.userData.city,
onChanged: !_status
? (newValue) => setState(
() {
user.userData.city = newValue;
},
)
: null,
hint: const Text("Ciudad"),
isExpanded: true,
),
);
i hope this helps understanding
stack trace
════════ Exception caught by widgets library ═══════════════════════════════════
The following RangeError was thrown building SearchableDropdown<int>(dirty, dependencies: [_InheritedTheme, _LocalizationsScope-[GlobalKey#f4db9], Directionality], state: _SearchableDropdownState<int>#8dd72):
RangeError (index): Invalid value: Only valid value is 0: 2
The relevant error-causing widget was
SearchableDropdown<int>
lib\…\profile_detail\profile_details.dart:181
When the exception was thrown, this was the stack
#0 List.[] (dart:core-patch/growable_array.dart:177:60)
#1 _SearchableDropdownState.build.<anonymous closure> package:searchable_dropdown/searchable_dropdown.dart:520
#2 List.forEach (dart:core-patch/growable_array.dart:313:8)
#3 _SearchableDropdownState.build
package:searchable_dropdown/searchable_dropdown.dart:517
#4 StatefulElement.build
package:flutter/…/widgets/framework.dart:4744
Hello @JHeisecke ,
Thanks for your explanations.
Here is an adaptation of your example for a minimalist but complete app:
import 'package:flutter/material.dart';
import 'package:searchable_dropdown/searchable_dropdown.dart';
class City {
String name;
int id;
City(
this.name,
this.id,
);
}
class CityData {
List<City> cities = [
City("Arrecife", 0),
City("Puerto del Carmen", 1),
City("Tias", 2),
City("Costa Teguise", 3),
City("Haria", 4),
];
CityData();
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
CityData cityData = CityData();
List<DropdownMenuItem<int>> cities;
MyHomePage({
Key key,
}) : super(key: key) {
cities = cityData.cities
.map(
(city) => DropdownMenuItem(
value: city.id,
child: Text(city.name),
),
)
.toList();
}
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int selectedCityIndex;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
SearchableDropdown.single(
items: widget.cities,
value: selectedCityIndex,
onChanged: (newValue) => setState(
() {
selectedCityIndex = newValue;
print("selected city id: ${widget.cities[selectedCityIndex].value}");
},
),
hint: const Text("Ciudad"),
searchFn: (String keyword, items) {
List<int> ret = [];
if (items != null && keyword.isNotEmpty) {
keyword.split(" ").forEach((k) {
int i = 0;
items.forEach((item) {
if (k.isNotEmpty &&
(item.child.data
.toString()
.toLowerCase()
.contains(k.toLowerCase()))) {
ret.add(i);
}
i++;
});
});
}
if (keyword.isEmpty) {
ret = Iterable<int>.generate(items.length).toList();
}
return (ret);
},
isExpanded: true,
),
],
),
),
);
}
}
I made several adaptations which may explain why this is working without errors for me.
Does this example run with errors for you?
I have got two objects, one for handling selected item value & the other is a list of items. During the initial screen loading time, both of these objects are null and the dropdown looks disabled . When I bind data to the list and perform selections, it is working fine. Issue comes when I try to reset the drop down when I have performed a save action and need to get all field values cleared. I tried to 1) set null to both objects 2) set null to selected item value alone 3) set -1 to selected item value. Nothing actually did work.
What makes me mad is, during the initial loading time both objects are null , it is working fine and during reset process, it shows the following error,
RangeError (index): Invalid value: Only valid value is 0: 13