Convert list of object with array to and from json file - flutter

I want to convert a list of 'Box' objects to a json file and also read it back in (json file to a list a 'Box' objects), but I'm a bit stuck on the implementation. I have written the below code, but I can only write a single 'Box' object to the json and convert a single 'Box' object back. When I try to do this with a list I hit errors like data that gets overwritten or just a single object that gets returned.
So in short, I want to write a List to json and convert json to List
I have the following data structure
Box model
class Box {
final String nameBox;
final List<Item> items;
Box({#required this.nameBox, #required this.items});
factory Box.fromJson(Map<String, dynamic> json) {
var items = json['items'];
List<Item> itemsList = items.cast<Item>();
return new Box(
nameBox: json["nameBox"],
items: itemsList
);
}
Map<String, dynamic> toJson() => {
"nameBox": nameBox,
"items": items,
};
}
Box fromJson(String boxData) {
return Box.fromJson(json.decode(boxData));
}
String toJson(Box box) {
return json.encode(box.toJson());
}
Item model
class Item {
final String itemName;
final int quantity;
Item({#required this.itemName, #required this.quantity});
factory Item.fromJson(Map<String, dynamic> json) {
return new Item(itemName: json["itemName"], quantity: json["quantity"]);
}
Map<String, dynamic> toJson() => {
"itemName": itemName,
"quantity": quantity,
};
}
Item fromJson(String itemData) {
return Item.fromJson(json.decode(itemData));
}
String toJson(Item item) {
return json.encode(item.toJson());
}
writeToJson function
Future writeJson(Box box) async {
final file = await _localFile;
List<Box> tempRead = await returnBoxes();
if (tempRead.isEmpty || tempRead == null) {
return;
}
tempRead.add(box);
file.writeAsString(json.encode(tempRead));
}
readJson function
Future<List<Box>> returnBoxes() async {
final file = await _localFile;
List<Box> boxList = new List<Box>();
Map<String, dynamic> content = await json.decode(file.readAsStringSync());
boxList.add(Box.fromJson(content));
return boxList;
}
I also tried to cast the json content to a list, but then I hit some iterable errors. Any who can help me out?

JSON has this idiosyncrasy that everything is either an object or an array, and you don't know what you get until you decode it. Dart decodes the two json types into a Map<String, dynamic> and List<dynamic> respectively. (The reason that you get dynamic is because each of them could itself then be a value, a json array or a json object, recursively.)
Dart encodes a Dart object by calling toJson on it and a Dart list by emitting a [ then each member of the list then a ].
Knowing that, it's easy to encode and decode arrays/lists. (I removed all the unnecessary code.)
class Box {
final String nameBox;
final List<Item> items;
Box({#required this.nameBox, #required this.items});
factory Box.fromJson(Map<String, dynamic> json) => Box(
nameBox: json['nameBox'],
items: json['items'].map<Item>((e) => Item.fromJson(e)).toList(),
);
Map<String, dynamic> toJson() => {
'nameBox': nameBox,
'items': items,
};
}
class Item {
final String itemName;
final int quantity;
Item({#required this.itemName, #required this.quantity});
factory Item.fromJson(Map<String, dynamic> json) => Item(
itemName: json['itemName'],
quantity: json['quantity'],
);
Map<String, dynamic> toJson() => {
'itemName': itemName,
'quantity': quantity,
};
}
Future writeJson(Box box) async {
final file = await _localFile;
var boxes = await returnBoxes();
/* I think you probably don't want this...
if (boxes.isEmpty || boxes == null) {
return;
}*/
// but rather, this
if (boxes == null) boxes = [];
boxes.add(box);
await file.writeAsString(json.encode(boxes));
}
Future<List<Box>> returnBoxes() async {
final file = await _localFile;
// because the json is an array (i.e. enclosed in []) it will be decoded
// as a Dart List<dynamic>
List<dynamic> d = json.decode(await file.readAsString());
// here we map the List<dynamic> to a series of Box elements
// each dynamic is passed to the Box.fromJson constructor
// and the series is formed into a List by toList
return d.map<Box>((e) => Box.fromJson(e)).toList();
}

Related

How to assign List<dynamic> to List<Khana>, where Khana is a model class in a parameter in Flutter-Firestore?

Actually, I am trying to get data from firebase and I am suffering from the Error:
Expected a value of type List < Khana >, but got one of type 'List< dynamic >'
I am getting data from the firebase, my fetchData function is:
Future<void> fetchAndSetOrder() async {
try {
await collectionRef.get().then((querySnapshot) {
for (var result in querySnapshot.docs) {
debugPrint("${result.runtimeType}=> ${result.data()}");
Orders newOrder = Orders.fromFirestore(result);
debugPrint("\n new order : $newOrder");
// _Order.add(newOrder);
debugPrint("new order added");
// _Order.add(Orders.fromMap(result as Map));
}
});
} catch (e) {
debugPrint("Error during Fetch:- $e");
}
}
and the Orders.fromFirestore constructor is:
factory Orders.fromFirestore(DocumentSnapshot<Object?> snapshot) {
final data = snapshot.data() as LinkedHashMap<String, dynamic>;
debugPrint("Inside From Firestore Function");
return Orders(
khana: data['khana'], // here is the error...
orderNumber: data['orderNumber'],
userId: data['userId'],
paymentCash: data['paymentCash'],
dateTime: data['dateTime'],
);
}
Orders class has:
class Orders{
List<Khana> khana; // this is another ModelClass
String userId;
int orderNumber;
DateTime dateTime;
bool paymentCash;
Orders({
required this.khana,
required this.userId,
required this.orderNumber,
required this.dateTime,
required this.paymentCash,
});
}
so, the issue is how can I read List from the firestore as a List ? Any other possible way to solve this issue.
My Khana Model is:
import 'dart:convert';
class Khana {
String mealName;
int id;
int price;
int quantity;
Khana({
required this.mealName,
required this.price,
required this.quantity,
required this.id,
});
Map<String, dynamic> toMap() {
final result = <String, dynamic>{};
result.addAll({'mealName': mealName});
result.addAll({'id': id});
result.addAll({'price': price});
result.addAll({'quantity': quantity});
return result;
}
factory Khana.fromMap(Map<String, dynamic> map) {
return Khana(
mealName: map['mealName'] ?? '',
id: map['id']?.toInt() ?? 0,
price: map['price']?.toInt() ?? 0,
quantity: map['quantity']?.toInt() ?? 0,
);
}
String toJson() => json.encode(toMap());
factory Khana.fromJson(String source) => Khana.fromMap(json.decode(source));
}
I am trying to read a List from the firestore snapshot.data(), it says it's return data type is List, and I want this list to be assigned to the List of my own ModelClass (i.e Khana), and I am not able to do that.
I even tried
factory Orders.fromFirestore(DocumentSnapshot<Object?> snapshot) {
final data = snapshot.data() as LinkedHashMap<String, dynamic>;
debugPrint("Inside From Firestore Function");
return Orders(
khana: data['khana'] as List<Khana>,
orderNumber: data['orderNumber'],
userId: data['userId'],
paymentCash: data['paymentCash'],
dateTime: data['dateTime'],
);
}
but got the same issue :(
Change your khana to this
khana: List<Khana>.from(data['khana'].map((x)=>Khana.fromJson(x)));

How to append 2 Future lists into one in Dart?

I am new to Dart and relatively new to coding. I would appreciate some advice on this.
I have 2 api calls and I would like to merge their results into a single list. I am still trying to grasp the concepts of futures, so far I have understood that I can't just add the returned lists. Here's my code:
class ApiClient {
final Client _client;
ApiClient(this._client);
dynamic get(String path) async {
final response = await _client.get(
Uri.parse(
'${ApiConstants.BASE_URL}$path?api_key=${ApiConstants.API_KEY}'),
headers: {
'Content-Type': 'application/json',
},
);
if (response.statusCode == 200) {
return json.decode(response.body);
} else {
throw Exception(response.reasonPhrase);
}
}
}
class ResultsModel1{
List<Model1>? names;
ResultsModel1({this.names});
ResultsModel1.fromJson(Map<String, dynamic> json) {
if (json['results'] != null) {
names = <Model1>[];
json['results'].forEach((v) {
names!.add(Model1.fromJson(v));
});
}
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
if (names != null) {
data['results'] = names!.map((v) => v.toJson()).toList();
}
return data;
}
}
class Model1{
final int id;
final int name;
const Model1({required this.id, required this.name});
factory Model1.fromJson(Map<String, dynamic> json){
return Model1(
id: json['id'],
name: json['name'],
);
}
Map<String, dynamic> toJson(){
final Map<String, dynamic> data = <String, dynamic>{};
data['id'] = id;
data['name'] = name;
return data;
}
}
class ResultsModel2{
List<Model2>? titles;
ResultsModel2({this.titles});
ResultsModel2.fromJson(Map<String, dynamic> json) {
if (json['results'] != null) {
titles = <Model2>[];
json['results'].forEach((v) {
titles!.add(Model2.fromJson(v));
});
}
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
if (titles != null) {
data['results'] = titles!.map((v) => v.toJson()).toList();
}
return data;
}
}
class Model2{
final int id;
final int name;
const Model2({required this.id, required this.title});
factory Model2.fromJson(Map<String, dynamic> json){
return Model2(
id: json['id'],
name: json['name'],
);
}
Map<String, dynamic> toJson(){
final Map<String, dynamic> data = <String, dynamic>{};
data['id'] = id;
data['name'] = name;
return data;
}
}
abstract class API1{
Future<List<Model1>> getModel1();
}
class API1Imp extends API1{
final ApiClient _client;
API1Imp(this._client);
#override
Future<List<Model1>> getModel1() async{
final response = await _client.get('/baseurlextension');
final names = ResultsModel1.fromJson(response).names;
return names ?? [];
}
}
abstract class API2{
Future<List<Model2>> getModel2();
}
class API2Imp extends API2{
final ApiClient _client;
API2Imp(this._client);
#override
Future<List<Model2>> getModel2() async{
final response = await _client.get('/baseurlextension');
final titles = ResultsModel2.fromJson(response).titles;
return titles ?? [];
}
}
I want to finally get a new list let's say ObjectModel[id, title] where model2 is appended below model1
class ObjectImpl {
final API1Imp api1;
final API2Imp api2;
ObjectImpl(this.api1, this.api2);
#override
List<ObjectModel>>> getObject() async {
try {
final names = await API1Imp.getModel1();
final titles = await API2Imp.getModel2();
final objects = names + titles;
return objects;
}
}
}
but I guess it doesn't work like that. Can anyone please help out?
When using the + operator you can't merge two different list types.
When does it like:
final names = await API1Imp.getModel1();
final titles = await API2Imp.getModel2();
final objects = names + titles;
it will give you an error because it's a different type.
instead, you can do it like
final names = await API1Imp.getModel1();
final titles = await API2Imp.getModel2();
final objects = [...names ,...titles];
If you want names and titles to be in the same list just do this:
final objects = [...names, ...titles];
Otherwise, you need to process them individually to add titles to names like so:
final objects = [];
for (int i = 0; i < names.length; i++) {
objects.add('${names[i]} ${titles[i]}');
}
If you want something other than this, then provide some examples so that we understand exactly what you want.
but I guess it doesn't work like that
Instead, it does! It all comes down on your data structure and on what you want to achieve.
You can merge lists with the + operator, but the two list types must match (e.g. you can do [1,2,3] + [4,5,6] obtaining a List<int>, but you can't do add ['a','b','c'] like that).
You can obtain a List<Object> - if that's what you want - using the spread operator like this:
final futureListOne = Future.delayed(Duration(milliseconds: 750), () => [1,2,3]);
final futureListTwo = Future.delayed(Duration(milliseconds: 950), () => ['a','b','c']);
final listOne = await futureListOne;
final listTwo = await futureListTwo;
final mergedList = [...listOne, ...listTwo];
print(mergedList); // [1, 2, 3, a, b, c]
But is having a List<Object> desirable in your use case? That depends on what you need. Usually, having loose types around your code makes it less readable / reusable, but it really depends on the context.
Note. We can use Future.wait to await for both the futures in parallel and increase efficiency.
final lists = await Future.wait([futureListOne, futureListTwo]);
final listOne = lists[0];
final listTwo = lists[1];
Hope this helps.
Thanks everyone for the answers. After some struggle it worked like this
final names = await API1Imp.getModel1();
final titles = await API2Imp.getModel2();
final objects = [...names ,...titles];

How can we save the data from a complex object in Firebase Firestore in Flutter?

I use complex objects to manage data in my app. For example I have an object defined by the "Defi class" (meaning Challenge in French).
Here is the Defi class :
class Defi {
final int modeApprentissage;
final DateTime date;
final double score;
final int seconds;
final List mots;
final List erreurs;
final List listes;
final int langue;
final int type;
final int typeMots1;
final int typeMots2;
const Defi(
{required this.modeApprentissage,
required this.date,
required this.type,
required this.langue,
required this.typeMots1,
required this.typeMots2,
required this.score,
required this.seconds,
required this.listes,
required this.mots,
required this.erreurs});
}
I have a LIST of Defi that I would like to save on FIREBASE FIRESTORE.
My question : Is it absolutely necessary to transform my list of Defi into a map to save it on Firestore ? Or is there another way ?
Here is how I do it :
List _defisMap = [];
for (Defi def in _defis) {
_defisMap.add({
'modeApprentissage': def.modeApprentissage,
'type': def.type,
'langue': def.langue,
'typeMots1': def.typeMots1,
'typeMots2': def.typeMots2,
'date': def.date,
'score': def.score,
'seconds': def.seconds,
'listes': def.listes,
'mots': def.mots,
'erreurs': def.erreurs,
});
}
if (await _connectivity.checkConnectivity() != ConnectivityResult.none) {
await FirebaseFirestore.instance
.collection('familyAccounts')
.doc(_accountEmail)
.update({
'defis': _defisMap,
});
I read in some article that in classes such as Defi, I could add a "factory" constructor ? Does this have anything to do with what I'd like to do ?
I created another class :
class Classes {
final String code;
final String nom;
const Classes({
required this.code,
required this.nom,
});
Map<String, dynamic> toJson() => {
'code': code,
'nom': nom,
};
factory Classes.fromMap(Map data) => Classes(
code: data['code'] ?? '',
nom: data['nom'] ?? '',
);
}
I save a list of Classes on Firestore. No problem.
But to retrieve this list : I must go from the list of maps that is on Firestore to a list of "Classes". And I just can't get the syntax right !
Here is my code :
final DocumentSnapshot<Map<String, dynamic>> docInfo =
await FirebaseFirestore.instance
.collection('familyAccounts')
.doc(_accountEmail)
.get();
_typeCompte =
docInfo['typeCompte'] == 'prof' ? TypeCompte.prof : TypeCompte.eleve;
_userId = docInfo['userId'];
_linked = docInfo['linked'];
_name = docInfo['name'];
_avatar = docInfo['avatar'];
_classe = docInfo['classe'];
_classeCode = docInfo['classeCode'];
_country = docInfo['country'];
_region = docInfo['region'];
docInfo['langue'] == 'french'
? _selectedIoLanguage = Language.french
: _selectedIoLanguage = Language.english;
_teacherCode = docInfo['teacherCode'];
_indexList = docInfo['indexList'];
_nbrList = docInfo['nbrList'];
_dateCreationCompte = docInfo['dateCreation'].toDate();
_defiTemp = docInfo['defis'].toList();
if (_typeCompte == TypeCompte.prof) {
_etablissement = docInfo['etablissement'];
_mesClasses = docInfo['mesClasses'];
(_mesClasses should be a list of "Classes").
I sense it should be some kind of xxx.map() etc.... but I don't master this syntax.
You need to create toJson method to set as a map of your list. If you have a list of Defi class. You can send it to map.
class Defi {
final int modeApprentissage;
final DateTime date;
final double score;
final int seconds;
final List mots;
final List erreurs;
final List listes;
final int langue;
final int type;
final int typeMots1;
final int typeMots2;
const Defi(
{required this.modeApprentissage,
required this.date,
required this.type,
required this.langue,
required this.typeMots1,
required this.typeMots2,
required this.score,
required this.seconds,
required this.listes,
required this.mots,
required this.erreurs});
Map<String, dynamic> toJson() => {
'modeApprentissage': modeApprentissage,
'type': type,
'langue': langue,
'typeMots1': typeMots1,
'typeMots2': typeMots2,
'date': date,
'score': score,
'seconds': seconds,
'listes': listes,
'mots': mots,
'erreurs': erreurs,
};
}
Your list name is should be List<Defi> _defis then map it with toJson. var jsonMap = _defisMap.map((e) => e.toJson()).toList();
var jsonMap = _defis.map((e) => e.toJson()).toList();
if (await _connectivity.checkConnectivity() != ConnectivityResult.none) {
await FirebaseFirestore.instance
.collection('familyAccounts')
.doc(_accountEmail)
.update({
'defis': jsonMap,
});
You can also Map it from api call with fromJson method. Here is the way. Add this to your Defi class.
factory Defi.fromJson(Map<String, dynamic> json) {
return Defi(
modeApprentissage: json['modeApprentissage'],
type: json['type'],
langue: json['langue'],
typeMots1: json['typeMots1'],
typeMots2: json['typeMots2'],
date: json['date'],
score: json['score'],
seconds: json['seconds'],
listes: json['listes'],
mots: json['mots'],
erreurs: json['erreurs'],);
}
And when you call api you need to call that function.
final Map<String, dynamic> jsonResult = json.decode(response.body);
//if it is a list returning from api. You can change variables what you got from api.
(jsonResult as List<dynamic>)
.map((data) => Defi.fromJson(data))
.toList();
// if it is not list
Defi.fromJson(jsonResult);
Yes, it's absolutely necessary to transform into a map. Firestore is a document store, the definition of a document is a json object, which is represented by a map in Dart. BUT, you don't have to do it "manually". Most of us use packages to do the mapping for us.
Freezed or JsonSerializable and then we simply call .toJson and pass that to the function. In addition to that Firestore now supports the .withConverter function so you don't have to do any conversion if you know what type the collection is storing.
So if you know the type of a collection is Defi you can do
final defyCollection = FirebaseFirestore.instance.collection('defis').withConverter<Defi>(
fromFirestore: (snapshot, _) {
final data = snapshot.data()!;
data['id'] = snapshot.id;
return Defi.fromJson(data);
},
toFirestore: (object, _) => object.toJson(),
)
This way you can simply use the defyCollection and the data property or function will be typed to your type.

How do I return the first five values from JSON response DART

I would like to return the first five value of name from the JSON response above
Future <List<UserModel>> fetchData() async {
final response =
await http.get(Uri.parse(URLCONST.API_URL));
if (response.statusCode == 200) {
List jsonResponse = json.decode(response.body);
return jsonResponse.map((user) => new UserModel.fromJson(user)).toList();
} else {
throw Exception('Unexpected error occured!');
}
}
Here's the model below.
class UserModel {
int id;
String name;
UserModel({
required this.id,
required this.name,
});
factory UserModel.fromJson(Map<String, dynamic> json) {
return UserModel(
id: json['id'],
name: json['name'],
);
}
}
API has several values in the list and I'd like to return the first 5 only
if i understood correctly, you can do something like this:
jsonResponse.map((user) => new UserModel
.fromJson(user))
// this will take only the first five elements of the iterable
.take(5)
// this will map each user to its corresponding name
.map((user) => user.name)
// this, as you already know, will convert the iterable to a list
.toList();
this will return a List<String> containing the first five names

Flutter error when converting a list to a json object removing some keys

I have an error when trying to convert a list of my object to json
My error:
Unhandled Exception: type 'RxList<ItemStockEntryModel>' is not a subtype of type 'Map<dynamic, dynamic>'
My model code:
class StockEntryModel {
final int? id;
final double costFreight;
final List<ItemStockEntryModel> items;
StockEntryModel({
this.id,
required this.costFreight,
required this.items,
});
factory StockEntryModel.fromJson(Map<String, dynamic> json) =>
StockEntryModel(
id: json['id'],
costFreight: json['costFreight'],
items: json['itemStockEntries'],
);
Map<String, dynamic> toJson() => {
'id': id,
'costFreight': costFreight,
'itemStockEntries': items,
};
Map<String, dynamic> itemsToMap() => {
'data': items,
};
String itemsToJson() {
var data = {};
final test = itemsToMap()['data'];
final mappedItems = Map<String, dynamic>.from(test) // the error occurs here on test variable
..removeWhere((key, value) => value == null || key == 'product');
print(json.encode(mappedItems));
data['itemStockEntries'] = mappedItems;
return json.encode(data);
}
}
my goal is to return a json object like this
// is not complete, only example...
{
"itemStockEntries": {
"data": [{
"id": 2
}, {
"id": 3
}]
}
}
but i need remove keys if this value is null and my key product..
I saw some similar errors, but I couldn't find the one that actually causes it
sorry for my bad english =(
My solution based on Loren codes. I expect to help someone also
Map<String, dynamic> toJson() => {
'id': id,
'costFreight': costFreight,
'itemStockEntries': items.map((e) => e.toJson()).toList(),
};
Map<String, dynamic> itemsToMap() => {
'data': items
.map(
(e) => e.toJson()
..removeWhere(
(key, value) => key == 'product' || value == null),
)
.toList(),
};
Map<String, dynamic> modelToJson() {
Map<String, dynamic> data = {};
data['itemStockEntries'] = itemsToMap();
data['costFreight'] = costFreight;
print(json.encode(data));
return data;
}
The .from method on a map needs a map to be passed into it, and you're passing in a list. So removeWhere is looking for keys and values which don't exist the way you're doing it.
So you could clear that first error getting rid of the itemsToMap function and changing the first 2 lines of your itemsToJson function to this.
var data = {'data': items}; // an actual map that you can pass in
final mappedItems = Map<String, dynamic>.from(data) // no more error here
But that's still a map with just a single key with a value of a list. So the removeWhere is not going to do anything of value here.
The List<ItemStockEntryModel> is what you need to be iterating through.
Assuming you have json serialization setup in your ItemStockEntryModel, this is closer to what you need to do. Not a complete example because I don't know what that model looks like, but it should give you the idea.
String itemsToJson() {
Map data = {};
List<String> jsonList = []; // new list of json strings to pass into data map
for (final item in items) {
if (// item meets whatever conditions you need) {
final jsonItem = json.encode(item);
jsonList.add(jsonItem);
}
}
data['itemStockEntries'] = {'data': jsonList};
return json.encode(data);
}