How to make dependent multilevel DropDown in flutter? - android

I am trying to make dependent multilevel dropdown first contains states list and second contains cities list, all the data fetched from API. Initially, I load state dropdown, when I select the state then cities of that state load if I select city, the city selected successfully but when I change the state value then an error occurs. What is the right way to reload the second dropdown if changes will make in the first dropdown?
Error: There should be exactly one item with [DropdownButton]'s value: Instance of 'City'.
Either zero or 2 or more [DropdownMenuItem]s were detected with the same value
Future _state;
Future _city;
#override
void initState() {
super.initState();
_state = _fetchStates();
}
Future<List<StateModel>> _fetchStates() async {
final String stateApi = "https://dummyurl/state.php";
var response = await http.get(stateApi);
if (response.statusCode == 200) {
final items = json.decode(response.body).cast<Map<String, dynamic>>();
List<StateModel> listOfUsers = items.map<StateModel>((json) {
return StateModel.fromJson(json);
}).toList();
return listOfUsers;
} else {
throw Exception('Failed to load internet');
}
}
Future<List<City>> _fetchCities(String id) async {
final String cityApi = "https://dummyurl/city.php?stateid=$id";
var response = await http.get(cityApi);
if (response.statusCode == 200) {
final items = json.decode(response.body).cast<Map<String, dynamic>>();
print(items);
List<City> listOfUsers = items.map<City>((json) {
return City.fromJson(json);
}).toList();
return listOfUsers;
} else {
throw Exception('Failed to load internet');
}
}
State Dropdown
FutureBuilder<List<StateModel>>(
future: _state,
builder: (BuildContext context,
AsyncSnapshot<List<StateModel>> snapshot) {
if (!snapshot.hasData)
return CupertinoActivityIndicator(animating: true,);
return DropdownButtonFormField<StateModel>(
isDense: true,
decoration: spinnerDecoration('Select your State'),
items: snapshot.data
.map((countyState) => DropdownMenuItem<StateModel>(
child: Text(countyState.billstate),
value: countyState,
))
.toList(),
onChanged:(StateModel selectedState) {
setState(() {
stateModel = selectedState;
_city = _fetchCities(stateModel.billstateid);
});
},
value: stateModel,
);
}),
City Dropdown
FutureBuilder<List<City>>(
future: _city,
builder: (BuildContext context,
AsyncSnapshot<List<City>> snapshot) {
if (!snapshot.hasData)
return CupertinoActivityIndicator(animating: true,);
return DropdownButtonFormField<City>(
isDense: true,
decoration: spinnerDecoration('Select your City'),
items: snapshot.data
.map((countyState) => DropdownMenuItem<City>(
child: Text(countyState.billcity)
.toList(),
onChanged: (City selectedValue) {
setState(() {
cityModel = selectedValue;
});
},
value: cityModel,
);
}),
class StateModel {
String billstateid;
String billstate;
String billcountryid;
StateModel({this.billstateid, this.billstate, this.billcountryid});
StateModel.fromJson(Map<String, dynamic> json) {
billstateid = json['billstateid'];
billstate = json['billstate'];
billcountryid = json['billcountryid'];
}
}
class City {
String billcityid;
String billcity;
String billstateid;
City({this.billcityid, this.billcity, this.billstateid});
City.fromJson(Map<String, dynamic> json) {
billcityid = json['billcityid'];
billcity = json['billcity'];
billstateid = json['billstateid'];
}

You have to make cityModel = null in onChanged callback of State dropdown.
setState(() {
cityModel = null;
stateModel = selectedState;
_city = _fetchCities(stateModel.billstateid);
});
There should be exactly one item with [DropdownButton]'s value:
Instance of 'City'. Either zero or 2 or more [DropdownMenuItem]s were
detected with the same value
This error occurs here, because the value you are passing not in the items of DropdownButtonFormField(city dropdown).
When you select a State, you are fetching new list of city list and passing it to CityDropDown but forgot to clear the previously selected city(cityModel).
You can also refer this example: DartPad

I am also facing a problem until new state data is not fetched It is showing previous state data. The approach I have used is different. I am not using future Builders.
Here is My code:
Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
new Expanded(
child: new Container(
width: 450,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
"Source",
style: TextStyle(
fontSize: 15, fontWeight: FontWeight.bold),
),
source1 != null ? DropdownButtonFormField<String>(
isExpanded: true,
validator: (value) => value == null ? 'field required' : null,
hint: Text("Select Source"),
items: source1.data.map((item) {
// print("Item : $item");
return DropdownMenuItem<String>(
value: item.descr,
child: Text(item.descr),
);
}).toList(),
onChanged: (String cat) {
setState(() {
subsourseStr = null;
sourceStr = cat;
getSubSource2(sourceStr);
});
},
value: sourceStr,
):SizedBox(height: 10),
],
),
),
)
],
),
),
//
Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
new Expanded(
child: new Container(
width: 450,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
"Sub Source",
style: TextStyle(
fontSize: 15, fontWeight: FontWeight.bold),
),
subSource2 != null ? DropdownButtonFormField<String>(
isExpanded: true,
validator: (value) => value == null ? 'field required' : null,
hint: Text("Select Sub Source"),
items: subSource2.data.map((item) {
return DropdownMenuItem<String>(
value: item.descr,
child: Text(item.descr),
);
}).toList(),
onChanged: (String cat) {
setState(() {
subsourseStr = cat;
});
},
value: subsourseStr,
):SizedBox(height: 10,),
],
),
),
)
],
),
),

Related

hi why my list item turn null in the futurebuilder of flutter?

Hi in my flutter app have FutureBuilder that return listview, my list listview create some button for update the hive table. when I click the first time on one of buttons everything is run smoothly, but when I click on same button again my hive key turn to null and program show my this error: "type 'Null' is not a subtype of type 'int' "
I write print all over my code but still I do not get it why the key turn null from the second time.
How can I Correct this? please help my.
my Futurebuilder body is:
FutureBuilder<List>(
future: controller.showTaskList(),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return SizedBox(
height: Get.height,
child: const Center(
child: CircularProgressIndicator(),
),
);
default:
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
} else {
List data = snapshot.data ?? [];
return ListView.separated(
scrollDirection: Axis.vertical,
physics:
const BouncingScrollPhysics(),
shrinkWrap: true,
itemCount: data.length,
itemBuilder: (context, index) {
// controller.taskIconCheckList
// .clear();
for (int i = 0;
i < data.length;
i++) {
if (data[i].status == true) {
controller.taskIconCheckList
.add(true.obs);
} else {
controller.taskIconCheckList
.add(false.obs);
}
}
return ListTile(
leading: Obx(
() => PageTransitionSwitcher(
transitionBuilder: (
child,
primaryAnimation,
secondaryAnimation,
) {
return SharedAxisTransition(
animation:
primaryAnimation,
secondaryAnimation:
secondaryAnimation,
transitionType:
SharedAxisTransitionType
.horizontal,
fillColor:
Colors.transparent,
child: child,
);
},
duration: const Duration(
milliseconds: 800),
child: controller
.taskIconCheckList[
index]
.value
? SizedBox(
child: IconButton(
icon: const Icon(
Icons
.check_circle_rounded,
color: Colors
.lightGreenAccent,
),
onPressed: () {
controller
.functionTaskIconCheckList(
index,
);
print('طول دیتا');
print(data.length.toString());
print('مقدار ایندکس');
print(index.toString());
print('مقدار کلید');
print(data[index].key.toString());
print(data[index].taskText.toString());
controller
.updateStatusTask(
index,
data[index]
.key); // here when i first click // return key currectly, but after that show null and updatestatusetask not run and show error.
},
),
)
: IconButton(
onPressed: () {
controller
.functionTaskIconCheckList(
index,
);
print('طول دیتا');
print(data.length.toString());
print('مقدار ایندکس');
print(index.toString());
print('مقدار کلید');
print(data[index].key.toString());
print(data[index].taskText.toString());
controller
.updateStatusTask(
index,
data[index]
.key); // here when i first click // return key currectly, but after that show null and updatestatusetask not run and show error.
},
icon: const Icon(
Icons
.radio_button_unchecked_outlined,
color: Colors.red,
),
),
),
),
title: Text(data[index].taskText,
style: normalTextForCategory),
subtitle: Text(
data[index]
.date
.toString()
.substring(0, 10),
textDirection:
TextDirection.ltr,
textAlign: TextAlign.right,
style: normalTextForSubtitle,
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
onPressed: () {
myDefaultDialog(
'هشدار',
'آیا از حذف این گزینه اطمینان دارید؟',
'بله',
'خیر',
() {
Get.back();
mySnakeBar(
'',
'گزینه مورد نظر با موفقیت حذف شد.',
Icons
.warning_amber_rounded,
Colors.yellow);
},
);
},
icon: const Icon(
Icons.delete),
color: Colors.redAccent,
),
IconButton(
onPressed: () {
Get.offNamed(
Routs.editTaskScreen,
arguments: 'edit');
},
icon: const Icon(
Icons.edit_calendar,
color:
Colors.yellowAccent,
),
),
],
),
);
},
separatorBuilder:
(BuildContext context,
int index) {
return const Divider(
height: 2,
color: Colors.white70,
);
},
);
}
}
},
),
this is my functionTaskIconCheckList form controller:
functionTaskIconCheckList(int index) {
taskIconCheckList[index].value = !taskIconCheckList[index].value;}
and this the updatestatusetask function
updateStatusTask(int index,int taskKey) async {
print('در تابع آپدیت ایندکس هست: ${index.toString()}');
print('در تابع آپدیت کی هست: ${taskKey.toString()}');
var taskBox = await Hive.openBox('task');
var filterTask = taskBox.values.where((task) => task.key == taskKey).toList();
Task task = Task(
filterTask[0].taskText,
filterTask[0].date,
taskIconCheckList[index].value,
filterTask[0].deleteStatus,
null,
null,
filterTask[0].taskCatId,
filterTask[0].userId);
await taskBox.put(taskKey, task);}
and this is my showtasklist function:
Future<List> showTaskList() async {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
var taskBox = await Hive.openBox('task');
var filterTask = taskBox.values
.where((task) => task.userId == sharedPreferences.getInt('key'))
.toList();
return filterTask;}
this is my model:
#HiveType(typeId: 2)
class Task extends HiveObject{
#HiveField(0)
String taskText;
#HiveField(1)
DateTime date;
#HiveField(2)
bool status;
#HiveField(3)
bool deleteStatus;
#HiveField(4)
int taskCatId;
#HiveField(5)
int userId;
#HiveField(6)
User? user;
#HiveField(7)
TaskCat? taskCat;
Task(this.taskText, this.date, this.status, this.deleteStatus, this.user,
this.taskCat, this.taskCatId, this.userId);
}
One possible solution would be to wait for the Future function to finish and then load the list. If it tries to load the list early before finishing up the Future function, it might presume the value to be null.
Hope this helps.
Still I do not know what is cause this problem, But I found an alternative temporary solution. I create temporary Int list. then just before the return listTile in the futureBuilder body, I write the Loop and save all of the key in that list. finally instead of pass the "data[index].key." I pass my key from that temporary Int list. so everything work fine now
this is my part of code change from before, but still I want know main solution.
return ListView.separated(
scrollDirection: Axis.vertical,
physics:
const BouncingScrollPhysics(),
shrinkWrap: true,
itemCount: data.length,
itemBuilder: (context, index) {
// controller.taskIconCheckList
// .clear();
for (int i = 0;
i < data.length;
i++) {
Get.find<
HomeScreenController>()
.taskKey.add(data[i].key);
if (data[i].status == true) {
Get.find<
HomeScreenController>()
.taskIconCheckList
.add(true.obs);
} else {
Get.find<
HomeScreenController>()
.taskIconCheckList
.add(false.obs);
}
}
return ListTile(

There should be exactly one item with [DropdownButton]'s value: 0. Either zero or 2 or more [DropdownMenuItem]s were detected with the same value

Please someone help me! I created DropdownButton with map key and values on StatefullWidget.
I have the following code producing an error when I run it
Settings_Form.Dart:
final user = Provider.of<User>(context);
return StreamBuilder<UserData>(
stream: DatabaseServices(uid: user.uid).userData,
builder: (context, snapshot) {
if(snapshot.hasData){
UserData? userData = snapshot.data;
return Form(
key: _formKey,
child: Column(
children: [
Text(
'Update your brew settings.',
style: TextStyle(fontSize: 18.0),
),
SizedBox(height: 20),
TextFormField(
initialValue: userData!.name,
decoration: textInputDecoration,
validator: (val) => val!.isEmpty ? 'Please enter a name' : null,
onChanged: (val) => setState(() => _currentName = val),
),
SizedBox(height: 10),
This is where I believe the problem is arising from. The DropdownField has all my troubles nested right in there. I am really stresses out.
DropdownButtonFormField(
value: userData.sugars,
decoration: textInputDecoration,
items: sugars.map((sugar) {
return DropdownMenuItem(
value: sugars,
child: Text('$sugar sugars'),
);
}).toList(),
onChanged: (val) => setState(() => _currentSugars = 'val' ),
),
SizedBox(height: 10),
Slider(
value: (_currentStrength ?? userData.strength).toDouble(),
activeColor: Colors.brown[_currentStrength ?? userData.strength],
inactiveColor: Colors.brown[_currentStrength ?? userData.strength],
min: 100.0,
max: 900.0,
divisions: 8,
onChanged: (val) => setState(() => _currentStrength = val.round()),
),
// slider
RaisedButton(
color: Colors.brown[400],
child: Text(
'Update',
style: TextStyle(color: Colors.white),
),
onPressed: () async {
if(_formKey.currentState!.validate()){
await DatabaseServices(uid: user.uid).updateUserData(
userData.sugars,
userData.name,
userData.strength
);
}
}
),
],
),
);
}else{
return Loading();
}
}
);
}
}
Database Class:
class DatabaseServices {
final String? uid;
DatabaseServices({ this.uid });
//collection reference
final CollectionReference brewCollection = Firestore.instance.collection('brews');
Future updateUserData (String sugars, String name, int strength) async {
return await brewCollection.document(uid).setData({
'sugars' : sugars,
'name' : name,
'strength': strength,
});
}
//get brew list from snapshot
List<Brew> _brewListFromSnapshot (QuerySnapshot snapshot) {
return snapshot.documents.map((doc){
return Brew(
name: doc['name'] ?? '',
sugars: doc['sugars'] ?? 0,
strength: doc['strength'] ?? '0',
);
}).toList();
}
//user data from snapshot
UserData _userDataFromSnapshot(DocumentSnapshot snapshot){
return UserData(
uid: 'uid',
name: snapshot.data['name'],
sugars: snapshot.data['sugars'],
strength: snapshot.data['strength']
);
}
//get brews Stream
Stream <List<Brew>> get brews{
return brewCollection.snapshots()
.map(_brewListFromSnapshot);
}
//get user doc stream
Stream<UserData > get userData {
return brewCollection.document(uid).snapshots()
.map(_userDataFromSnapshot);
}
}
Error Message:
Either zero or 2 or more [DropdownMenuItem]s were detected with the same value
'package:flutter/src/material/dropdown.dart':
Failed assertion: line 1506 pos 15: 'items == null || items.isEmpty || value == null ||
items.where((DropdownMenuItem<T> item) {
return item.value == value;
}).length == 1'

Incorrect use of ParentDataWidget | Flutter | DropDown Button

I got an error Incorrect use of parentdata widget when trying to add a DropdownButton within a row Widget.
Here I've added two elements, Text Widget and DropdownButton within a Row Widget
Row(
children: <Widget>[
Expanded(flex: 1, child: Text(' Source :')),
Expanded(
flex: 4,
child: FutureBuilder<SourceData>(
future: sourceData,
builder: (context, snapshot) {
return sourceDropDownList(snapshot.data.sources);
return CircularProgressIndicator();
})
) // FutureBuilder
],
), // Row
Here's the DropDownList Function returning a DropdownButton
Widget sourceDropDownList(List<Sources> sources) {
var sourceNameList = List<String>();
for (var i = 0; i < sources.length; i++) {
sourceNameList.add(sources[i].name);
}
return DropdownButton<String>(
value: dropdownValue,
onChanged: (String newValue) {
setState(() {
dropdownValue = newValue;
});
},
items: sourceNameList.map((value) {
return DropdownMenuItem(
value: value,
child: Text(value),
);
}).toList(),
);
}
Here is the screenshot of exact error message :
[Error Message][1]
[Actual Representation of widget][2]
[1]: https://i.stack.imgur.com/w9QsX.png
[2]: https://i.stack.imgur.com/ALAOT.png
You should define your list type.
items: sourceNameList.map((String value) {
return new DropdownMenuItem(
value: value,
child: new Text(
value.name,
)
);
}).toList(),

Flutter- Make the app show the previously fetched data when device is offline

I'm new to Flutter and need a bit of help. I've built a random joke generator app that reads data from the API and displays a new joke every time a button is pressed. I want to make the app show the previously fetched data when device is offline. I tried searching online but found nothing that does it using Flutter.
class _HomePageState extends State<HomePage> {
List data;
Future<Jokes> post;
String url="https://official-joke-api.appspot.com/random_joke";
var response;
Future<Jokes> getData() async {
response =
await http.get(url, headers: {"Accept": "application/json"});
if (response.statusCode == 200) {
return Jokes.fromJson(json.decode(response.body));
} else {
throw Exception('Failed to load post');
}
}
changeApi()
{
setState(() {
if (response.statusCode == 200) {
return Jokes.fromJson(json.decode(response.body));
} else {
throw Exception('Failed to load post');
}
});
}
#override
void initState()
{
super.initState();
this.getData();
}
#override
Widget build(BuildContext context) {
final key = new GlobalKey<ScaffoldState>();
// TODO: implement build
return Scaffold(
key: key,
backgroundColor: Colors.amberAccent,
body: new Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new FutureBuilder<Jokes>(
future:
getData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
//checks if the response returns valid data
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new GestureDetector(
child: new Text(
snapshot.data.setup ,
style: TextStyle(fontFamily: "Rock Salt"),
),
onLongPress: ()
{
Clipboard.setData(new ClipboardData(text: snapshot.data.setup, ));
key.currentState.showSnackBar(
new SnackBar(content: new Text("Copied to Clipboard"),));
},
),
/
SizedBox(
height: 10.0,
),
new GestureDetector(
child: new Text(
" - ${snapshot.data.punchline}",
style: TextStyle(fontFamily: "Roboto"),
),
onLongPress: ()
{
Clipboard.setData(new ClipboardData(text: snapshot.data.punchline));
key.currentState.showSnackBar(
new SnackBar(content: new Text("Copied to Clipboard"),));
},
),
],
),
);
} else if (snapshot.hasError) {
//checks if the response throws an error
return Text("${snapshot.error}");
}
return CircularProgressIndicator();
},
),
SizedBox(
height: 25.0,
),
new RaisedButton(
onPressed: changeApi,
color: Colors.pinkAccent,
child: Text("Press for a new joke", style: TextStyle(color: Colors.white,)),
)
],
),
),
);
}
}
class Jokes {
final String setup;
final String punchline;
Jokes({this.setup, this.punchline});
factory Jokes.fromJson(Map<String, dynamic> json) {
return Jokes(setup: json['setup'], punchline: json['punchline']);
}
}
Api
Here's my full code: code
There are some videos about caching, here's the one from flutter team, and one from tensor programming channel.
You can use connectivity plugin to check whether the device is offline.
If device is offline, show data from shared_preferences or sqflite, if it's online, fetch new data (and of course update your cache).

Flutter - RangeError(index)

I get an error while I'm building a ListView. In this flutter app I try to count for each column some points when a button is clicked. But I'm getting always the same error.
══╡ EXCEPTION CAUGHT BY GESTURE
I/flutter (28729): The following RangeError was thrown while handling
a gesture: I/flutter (28729): RangeError (index): Invalid value: Valid
value range is empty: 0
This is my code and I hope somebody is able to help me fixing the error:
import 'package:flutter/material.dart';
class Punktezaehler extends StatefulWidget{
final List<String> spieler_namen;
Punktezaehler(this.spieler_namen);
#override
State<StatefulWidget> createState() => new _Punktezaehler(this.spieler_namen);
}
class _Punktezaehler extends State<Punktezaehler>{
final List<String> spieler_namen;
_Punktezaehler(this.spieler_namen);
List<int> punkteanzahl_teamEins = [];
List<int> punkteanzahl_teamZwei = [];
int team1_hinzugezaehlt = 0;
int team2_hinzugezaehlt = 0;
#override
Widget build(BuildContext context) {
var spieler1 = spieler_namen[0].substring(0,3);
var spieler2 = spieler_namen[1].substring(0,3);
var spieler3 = spieler_namen[2].substring(0,3);
var spieler4 = spieler_namen[3].substring(0,3);
return new Scaffold(
appBar: new AppBar(
automaticallyImplyLeading: false,
title: new Text("$spieler1 & $spieler2 vs" +" $spieler3 & $spieler4"),
actions: <Widget>[
],
),
body: Container(
child: new Row(
children: <Widget>[
new Column(
children: <Widget>[
new IconButton(
icon: Icon(Icons.exposure_plus_2),
onPressed: () => punkte_hinzuzaehlen(1, 2)
)
],
),
new Padding(padding: EdgeInsets.only(left: 100.0)),
new Expanded(
child: ListView.builder(
itemCount: punkteanzahl_teamEins.length, //--> Error is thrown here
itemBuilder: (context, index){
return Text(punkteanzahl_teamEins[index].toString());
}
),
),
new Expanded(
child: ListView.builder(
itemCount: punkteanzahl_teamZwei.length, //--> Error is thrown here
itemBuilder: (context, index){
return Text(punkteanzahl_teamZwei[index].toString());
}
),
),
new Column(
children: <Widget>[
new IconButton(
icon: Icon(Icons.exposure_plus_2),
onPressed: () => punkte_hinzuzaehlen(2, 2)
)],
)
],
)
),
);
}
void punkte_hinzuzaehlen(int team, int nummer){
if (team == 1){
setState(() {
punkteanzahl_teamEins[team1_hinzugezaehlt] = nummer;
team1_hinzugezaehlt++;
});
}
else if(team == 2){
setState(() {
punkteanzahl_teamZwei[team2_hinzugezaehlt] = nummer;
team2_hinzugezaehlt++;
});
}
}
}
the problem that when you click a button you are calling this line
punkteanzahl_teamEins[team1_hinzugezaehlt]
and team1_hinzugezaehlt have an initial value of 0 but every time the user click the button this value will increase by one
so let's say your punkteanzahl_teamEins list contains 2 items in the fourth click the value team1_hinzugezaehlt will be 4 witch will cause this error . so the solution is to check whether the value is in the range or not
if (team1_hinzugezaehlt<punkteanzahl_teamEins.length){
setState(() {
punkteanzahl_teamEins[team1_hinzugezaehlt] = nummer;
team1_hinzugezaehlt++;
});
}
and do the same for the second function

Categories

Resources