Flutter newbie here. I am having a hard time trying to decode a local json list to model objects.
I am successful so far with showing what I want to show with something like:
myData[index]['id']
myData[index]['name']
But I would like to use a model object that I already created like:
rule.id
rule.name
And eventually passing the rule object through the onTap event.
This is the code I used to load json: future:DefaultAssetBundle.of(context).loadString('assets/rule.json'),
This is the code inside a FutureBuilder.
var myData = json.decode(snapshot.data);
return ListView.builder(
itemBuilder: (BuildContext context, int index) {
return Padding(
padding: const EdgeInsets.all(10.0),
child: ListTile(
leading: Icon(Icons.people),
title: Text(myData[index]['id']),
subtitle: Text(myData[index]['name']),
trailing: Icon(Icons.keyboard_arrow_right),
onTap: () => RuleDetailPage.show(context),
),
);
},
itemCount: myData == null ? 0 : myData.length,
);
},
)
);
The fromJson method looks like this:
factory Rule.fromJson(Map json) {
return Rule(
id: json['id'],
name: json['name'],
);
}
}
It looks like you're expecting a list of JSON objects. Just cast the result of decode to a list, and then map each JSON object into a Rule by passing it into your factory.
Like so:
var myData = json.decode(snapshot.data) as List;
List<Rule> rules = list.map((item) {
return Rule.fromJson(item);
}).toList();
return ListView.builder(
itemBuilder: (BuildContext context, int index) {i
Rule currentRule = rules[index];
return Padding(
padding: const EdgeInsets.all(10.0),
child: ListTile(
leading: Icon(Icons.people),
title: Text(currentRule.id),
subtitle: Text(currentRule.name),
trailing: Icon(Icons.keyboard_arrow_right),
onTap: () => RuleDetailPage.show(context),
),
);
},
itemCount: myData == null ? 0 : rules.length,
);
I think you are looking for a json parser: https://app.quicktype.io/
This will allow you to create a model class, and then you will be able to call your objects in the way that you suggest.
If I understood your question, you already did most of the job ;-)
json.decode(snapshot.data) return a Json object which can be a Map<String, dynamic> or a List<dynamic> regarding if the root node of your json is an object or an array.
It seems that you already implemented the fromJson factory method which is the way to go except if you want to try a parsing library as mentioned by #code-poet
So just assemble all parts of your code: var rules = Rule.fromJson(json.decode(snapshot.data)); et voilĂ !
If your snapshot.data contains an array of Rule then as mentioned json.decode returns a list and you must write: json.map((j) => Rule.fromJson(j)).toList();
Related
I'm a newbie Flutter programmer.
Im trying to make a generic grid widget that let me pass an object and create columns and rows dynamically.
I've made it work with an specific object but what i need is to read the names of attributes of an object to dynamically create grid column names and values so i can use the same grid for client or articles or everything.
For example i have a class for clients
class Client {
int id;
String name;
String mail;
Client({required this.id, required this.name, required this.mail});
}
then in my code retrieve a list of clients and pass that to the grid widget as List.
I just need to know how to loop the object recieved to:
get the list of fields names (id, name, mail)
then get its value for example something like
var field_name = element;
obj.field_name.value;
Hope you can understand what im trying to do.
Thank you in advance.
you can try this code
FutureBuilder<List<Client>>(
future: // your future method which return the List<Client>,
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (context, index) {
final client= snapshot.data![index];
return ListTile(
title: Text(client.name),
leading: CircleAvatar(
child: Text(client.id),
),
subtitle: Text(client.email),
);
},
);
} else if (snapshot.hasError) {
return Center(
child: Text(snapshot.error.toString()),
);
}
return Center(
child: CircularProgressIndicator(),
);
},
},),
let me know if this is working for you.
Say you are getting List of Client Object and Class looks like this
class Client {
int id;
String name;
String mail;
Client({required this.id, required this.name, required this.mail});
}
Now if your question is how can i differentiate some Client from the list and have a separate Grid widget for them?
If yes, lets take example with above given class
We have Client with category, Design, Development etc.
We can simply have a another variable in class such as
class Client {
int id;
String name;
String mail;
//type variable to differentiate , could be String or enum if you know exactly
String type;
Client({required this.id, required this.name, required this.mail});
}
and
GridView.builder(
itemCount: images.length,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 4.0,
mainAxisSpacing: 4.0
),
itemBuilder: (BuildContext context, int index){
Client item = Clients[index];
if(item.type == 'Design')
{
// display designClient item
return DesignGrid();
}
if(item.type == 'Development')
{
// display developmentType item
return DevelopmentGrid();
}
}
)
I'm trying to build a flutter view that loads a list of items ('cost codes' in the code snippet) from a database call. This code works elsewhere in my project where I already have data in the database, but it fails when it tries to read data from an empty node. I can provide dummy data or sample data for my users on first run, but they might delete the data before adding their own, which would cause the app to crash the next time this view loads.
What's the proper way to deal with a potentially empty list in a StreamBuilder?
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: dbPathRef.onValue,
builder: (context, snapshot) {
final costCodes = <CostCode>[];
if (!snapshot.hasData) {
return Center(
child: Column(
children: const [
Text(
'No Data',
style: TextStyle(
color: Colors.white,
),
)
],
),
);
} else {
final costCodeData =
// code fails on the following line with the error
// 'type "Null" is not a subtype of type "Map<Object?, dynamic>" in type cast'
(snapshot.data!).snapshot.value as Map<Object?, dynamic>;
costCodeData.forEach(
(key, value) {
final dataLast = Map<String, dynamic>.from(value);
final account = CostCode(
id: dataLast['id'],
name: dataLast['name'],
);
costCodes.add(account);
},
);
return ListView.builder(
shrinkWrap: false,
itemCount: costCodes.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(
costCodes[index].name,
style: const TextStyle(color: Colors.white),
),
subtitle: Text(
costCodes[index].id,
style: const TextStyle(color: Colors.white),
),
);
},
);
}
},
);
}
Personally I tend to avoid handling raw data from a database in the UI code and handle all of this in a repository/bloc layer.
However, to solve your issue you can simply add a ? to the end of the cast like so:
final costCodeData = (snapshot.data!).snapshot.value as Map<Object?, dynamic>?;
You will no longer get the cast exception - however you still have to test costCodeData for null.
This block of code may help:
final data = snapshot.data;
final Map<Object?, dynamic>? costCodeData
if (data == null) {
costCodeData = null;
} else {
costCodeData = (snapshot.data!).snapshot.value as Map<Object?, dynamic>?;
}
if (costCodeData == null){
// Show noData
} else {
// Show data
}
final dataLast = Map<String, dynamic>.from(value);
final account = CostCode(
id: dataLast['id'],
name: dataLast['name'],
);
costCodes.add(account);
},
you declaired dataLast with a Map having key as String, but inside the account variable the id and name are not in the string format, keep those inside "" || '' even after modiying these, if you still face other issue try putting question mark at the end of the line
(snapshot.data!).snapshot.value as Map<Object, dynamic>?
Getting this error with this:
return ListView(
children: snapshot.data!.docs.map((index, DocumentSnapshot document) {
...
.toList()
}
Without index, everything works fine
How would be possible to get an index here? Should I use a different loop to get it?
the children parameter of listView accepts a List as parameter your snapshot.data!.docs.map((index, DocumentSnapshot document) returns a List<dynamic> which is obvious because you haven't created widget list yet.
your method should be like this:
return ListView(
children: snapshot.data!.map((value) => AnyWidget(value)).toList(),
);
Edit
I got your point. Assuming index is the index of list.
return ListView(
children: snapshot.data!.map((document) {
var index = snapshot.data!.indexOf(document);
return Text('abc');
}).toList()
);
better you convert snapshot.data into a List<yourModel> and use that instead of directly using it here.
i think it should be return <Widget>
Ex:
return ListView(
children: data.map((dynamic value) {
// Handle data
...
// Return <Widget>
return Text('$value');
}).toList(),
);
I used ListView.builder:
return ListView.builder(
itemCount: snapshot.data!.docs.length,
itemBuilder: (context, index) {
Here the logic changes a bit and since I don't iterate over the list I need to assign it to a new var every time:
Map<String, dynamic> set = snapshot.data!.docs[index].data()! as Map<String, dynamic>;
I Wanna ask for my problem in flutter. How to append new data from another screen (has pop) to main screen (contain list view data from json) this is my code from main screen.
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (BuildContext context) => FormUser()))
.then((VALUE) {
/////////
Here for adding new data from VALUE into list view
/////
});
});
This my listview
Widget _buildListView(List<User> users) {
return ListView.separated(
separatorBuilder: (BuildContext context, int i) =>
Divider(color: Colors.grey[400]),
itemCount: users.length,
itemBuilder: (context, index) {
User user = users[index];
return ListTile(
leading: Icon(Icons.perm_identity),
title: Text(capitalize(user.fullName)),
subtitle: Text(user.gender),
trailing: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Text(user.grade.toUpperCase()),
Text(user.phone)
],
),
);
},
);
}
}
This is return data from add screen
User{id: null, fullname: Camomi, gender: pria, grade: sma, phone: 082232, email: ade#gmul.com}
This is my class model of typedata
class User {
int id;
String fullName, gender, grade, phone, email;
User(
{this.id,
this.fullName,
this.gender,
this.grade,
this.phone,
this.email});
}
You add an element to your list of users. Then you call setState so your widget's build function gets called and builds the new view with the list of users that now contains the new element.
And please do not use .then() in your onPressed method. Make the method async and use await or if you really want to use .then(), at least return the Future it gives you.
You will need to do extra steps:
Wait for the result when calling push() method:
final result = await Navigator.push(...);
In your FormUser widget, when you finish entering data and press on Done or similar, you should return the created user:
// userData will be assigned to result on step 1 above. Then you add that result (user) to your list
Navigator.pop(context, userData);
You can find a very good tutorial here.
First of all make sure that Your class extends StatefulWidget.
I assume your list of users looks like this (or it's empty like [] ) :
List<User> users = [user1, user2, user3];
In Your FloatingActionButton in onPressed you should have this method (copy the content to onPressed or write a method like this somewhere in your code).
The method must be async to await the user
void _addUser() async {
final user = await Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) => FormUser()));
if (user != null) {
setState(() => users.add(user));
}
}
This code should work fine, enjoy :)
In my code I added a dropdown which looks like below, When I switched the selection in dropdown its not getting updated its showing exception, I have declared a variable in statefull widget , In my dropdown function I am assigning that as A value to the dropdown Button, and in Onchanged I am passing the json to another function there I am taking the value from a variable and assigning it to a opSelected variable inside the setState
class _ReportFilterState extends State<ReportFilter> {
String opSelected;
//declared string to hold the selected value in dropdown within the state class.
buildMainDropdown(List<Map<String, Object>> items, StateSetter setState) {
return Container(
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 27.0,
vertical: 16.0,
),
child: Align(
alignment: Alignment.topLeft,
child: DropdownButtonHideUnderline(
child: DropdownButton(
isExpanded: true,
hint: Text("Choose Filters"),
value: opSelected, // Here assigning the value
items: items
.map((json) => DropdownMenuItem(
child: Text(json["displayName"]), value: json))
.toList(),
onChanged: (json) {
manageIntState(json, setState);
},
),
),
),
),
);
}
void manageIntState(Map<String, Object> jsonSelected, StateSetter setState) {
setState(() {
dispName = jsonSelected["displayName"];
//here I am setting the selected value
opSelected = dispName;
//Doing some operations
id = jsonSelected['id'];
type = jsonSelected['type'];
selectedFilterOption = jsonSelected;
if (jsonSelected.containsKey("data")) {
List<Map<String, Object>> tempList;
List<String> dailogContent = List<String>();
tempList = jsonSelected['data'];
tempList
.map((val) => {
dailogContent.add(val['displayId']),
})
.toList();
_showReportDialog(dailogContent);
}
});
}
But when I run I will end up with error
items==null||
items.isEmpty||value==null||itsems.where((DropdownMenuItem
item)=>item.value==value).length==1 is not true ..
Let me know what I have done wrong in code so its giving me like this, if I commented its not showing the selected dropdown value.
That error happens when the selected value of the DropdownButton is not one of the values of it's items.
In your case, your items values are json which is a Map<String, Object>, and the value of the DropdownButton is opSelected which is a String.
So you need to change the type of opSelected like this:
Map<String, Object> opSelected;
Also make sure you are passing a reference of the same list of items to buildMainDropdown(), because if you are creating a new list while calling buildMainDropdown() then the DropdownButton will have another reference of options and it's not allowed
Note: you may want yo use dynamic instead of Object for the Map, like this:
Map<String, dynamic> opSelected;
Here is why: What is the difference between dynamic and Object in dart?