Best practice for JSON deserialization - flutter

I have two different endpoints.
"/api/users?page=1" with the response below :
{"users": [{"id":1, "name": "David"}, {"id": 2, "name": "Richard"}]}
"/api/pictures?page=2" with this response :
{"pictures": [{"userID": 1, "urls": ["https://b.co/c.png", "https://a.com/b.png"]}]}
What is the best solution to merge this two endpoints in OOP.
class User {
final int id;
final String name;
User(this.id, this.name);
}
class Picture {
final User user;
final List<String> pictures;
Picture(this.user, this.pictures);
factory Picture.fromJson(Map<String, dynamic> json) {
return Picture(
// Here I have to find json['userID'] from previously fetched users. I can use singletons but its an anti-pattern and not preferred.
, json['urls']
);
}
}
I'm not gonna change my server responses. please help me out.

I would await for both API returns and then have it be passed inside Picture class. Also, I would implement fromJson on User class too
class User {
final int id;
final String name;
User(this.id, this.name);
User.fromJson(Map<String, dynamic>) {
// .....
}
}
class Picture {
final User user;
final List<String> pictures;
Picture(this.user, this.pictures);
factory Picture.fromJson({
Map<String, dynamic> pictureJson,
Map<String, dynamic> userJson
}) {
final user = User.fromJson(userJson);
return Picture(
user: user;
// Here I have to find json['userID'] from previously fetched users. I can use singletons but its an anti-pattern and not preferred.
, json['urls']
);
}
}

Related

Riverpod Model from List<Model> to Map<String>

I am new to flutter and I am a bit confused about Riverpod and have wasted a few days on this issue which is probably really easy. I have a Model, Provider and Service created with Riverpod which I will share below. I have a widget that takes a Map and an API that is structured
{
"job": [
{"feild1": "data",..},
{"feild2": "data",..},
{"feild3": "data",..}
]
}
It is being mapped as List how can I change that to Map for a child widget I have created.
This is my Provider:
final jobsDataProvider = FutureProvider<List<JobsModel>>((ref) async {
return ref.watch(jobsProvider).getJobs();
});
This is my model:
class JobsModel {
final String jobid;
final String from_state;
final String from_suburb;
final String to_state;
final String to_suburb;
final String travel_time;
final String date;
final String distance;
final String status;
final String map;
JobsModel({
required this.jobid,
required this.from_state,
required this.from_suburb,
required this.to_state,
required this.to_suburb,
required this.travel_time,
required this.date,
required this.distance,
required this.status,
required this.map,
});
factory JobsModel.fromJson(Map<String, dynamic> json) {
return JobsModel(
jobid: json['jobid'],
from_state: json['from']['state'],
from_suburb: json['from']['suburb'],
to_state: json['to']['state'],
to_suburb: json['to']['suburb'],
travel_time: json['travel_time'],
date: json['date'],
distance: json['distance'],
status: json['status'],
map: json['map'],
);
}
}
This is my service:
class ApiServices {
String endpoint = 'https://localhost:3000/jobs';
Future<List<JobsModel>> getJobs() async {
Response response = await get(Uri.parse(endpoint));
if (response.statusCode == 200) {
final List result = jsonDecode(response.body)['jobs'];
return result.map(((e) => JobsModel.fromJson(e))).toList();
} else {
throw Exception(response.reasonPhrase);
}
}
}
final jobsProvider = Provider<ApiServices>((ref) => ApiServices());
My child widget takes a Map<String, dynamic> how can I make this work so I can map multiple widgets from the returned api call into a row.
Thanks heaps all.

How to do a toJson encode method on Dart/Flutter?

I used the fromJson method to recover a Struct with a List from Json decode http request and receiver it on my class, but now i want to do a reverse, i want to pass the data on my class to my toJson method and send him to a Json encode http POST. Please, i new on Dart/Flutter, someone know how to do this?
import 'dart:convert';
List<Itens> userFromJson(String str) =>
List<Itens>.from(jsonDecode(str).map((x) => Itens.fromJson(x)));
class Coletas {
final int codigo;
final String dataIni;
late String? dataFin;
late String? status;
final List<Itens> itemList;
Coletas(
{
required this.dataIni,
this.dataFin,
this.status,
required this.codigo,
required this.itemList
}
);
factory Coletas.fromJson(Map<String, dynamic> json) {
return Coletas(
dataIni: json['dtData'],
codigo: json['iCodigo'],
itemList: List<Itens>.from(json['stItens'].map((x) => Itens.fromJson(x))),
);
}
Map<String, dynamic> toMap() {
return {
'codigo': codigo,
'dataIni': dataIni,
'dataFin': dataFin,
'status': status
};
}
}
class Itens {
final int? id;
final int codigo;
late int quantidade;
late String? status;
final String codigoEAN;
Itens({
this.id,
this.status,
required this.codigo,
required this.codigoEAN,
required this.quantidade,
});
Map<String, dynamic> toJson(){
return {
'icodigo' : codigo,
'sCodigoBarras': codigoEAN,
'iQtd': quantidade
};
}
factory Itens.fromJson(Map<String, dynamic> json) {
return Itens(
codigo: json['iCodigo'],
codigoEAN: json['sCodigoBarras'],
quantidade: json['iQtd'],
);
}
Map<String, dynamic> toMap() {
return {
'id': id,
'status': status,
'codigo': codigo,
'codigoEAN': codigoEAN,
'quantidade': quantidade,
};
}
}
I tried to pass ever item on List separeted so, but not happen i expected.
Map<String, dynamic> toJSon(Coletas value) =>
{
'dtData' : dataIni,
'iCodigo': codigo,
'stItens': [],
};
For a better structure - format and use you can look at the flutter serialization documentation : https://docs.flutter.dev/development/data-and-backend/json.
It explains how to create your model and how to generate them to create fromJson and toJson Model based on the defined data. (https://docs.flutter.dev/development/data-and-backend/json#creating-model-classes-the-json_serializable-way)
It will helps you with your parsing - sending - receiving data.
I think you should assign Coletas as
Map<String, dynamic> toJSon(Coletas value) =>
{
'dtData' : value.dataIni,
'iCodigo': value.codigo,
'stItens': value.itemList,
};

Firebase Realtime database serialization in Flutter

I'm struggling a bit with getting data I push to Firebase Realtime DB in Flutter.
I'm using this code to push data to FB:
DatabaseReference newPostRef = news_dbRef.push();
final newKey = news_dbRef.child('News').push().key;
newPostRef.set({
"timestamp": timestamp,
"content": content_u,
"title": title_u,
"imgURL": imageUrl_u.substring(0,imageUrl_u.lastIndexOf('?')),
"fileURL": fileUrl_u.substring(0,fileUrl_u.lastIndexOf('?')),
"user": _user
});
so it creates a desired object in Firebase like this:
screenshot from Firebase
Now when I'm trying to get this data to my app, I'm having issues with proper serialization with it.
This is what I'm doing:
DatabaseReference newsCountRef =
FirebaseDatabase.instance.ref().child('News');
newsCountRef.onValue.listen((DatabaseEvent event) {
var data = event.snapshot.value;
String encoded = jsonEncode(data);
Map<String, dynamic> postslist = jsonDecode(encoded);
var somelist = postslist.entries.map((e) => TestNewsModel(e.key, e.value)).toList();
so it brings me to a stage that I have a list... but cannot read values for each line.
Do you have any ideas what I'm missing? Here's a class I'm using for serialization:
class TestNewsModel {
String recordid;
dynamic fields;
TestNewsModel(this.recordid, this.fields);
String toString() {
return '{ ${this.recordid}, ${this.fields} }';
}
}
class Field {
String timestamp;
String content;
String title;
String imgURL;
String fileURL;
String user;
Field({
required this.timestamp,
required this.content,
required this.title,
required this.imgURL,
required this.fileURL,
required this.user,
});
String toString() {
return '{ ${this.timestamp}, ${this.content}, ${this.title}, ${this.imgURL}, ${this.fileURL}, ${this.user} }';
}}
Would recommend creating a .fromDoc method in your class, similar to how you would create a .fromJson method.
Heres an example from one of my projects, this way you can avoid encoding and decoding.
///Creates a [Patient] from the information from a single firestore doc.
factory Patient.fromDoc(doc) {
return Patient(
doc.data()['email'],
doc.data()['forename'],
doc.data()['surname'],
doc.data()['hospitalNum'].toString(),
doc.id,
);
}

Inheritance in Null Safety - Flutter

I'm working on Flutter Clean Architecture. Initially I developed my project without sound null-safety. I recently migrated it to null-safety.
Migrating to null-safety was pretty smooth but unfortunately there is an issue with inheriting classes that I'm unable to resolve.
Here is a sample of my code I'm working with:
ENTITIES:
UserEntity:
class UserEntity extends Equatable {
final int userID;
final String email;
const UserEntity({required this.userID, required this.email});
#override
List<Object> get props => [userID, email];
}
CourseEntity:
class CourseEntity extends Equatable {
final int courseID;
final String courseName;
const CourseEntity({required this.courseID, required this.courseName});
#override
List<Object> get props => [courseID, courseName];
}
DataEntity:
class DataEntity extends Equatable {
final UserEntity? user;
final CourseEntity? course;
const DataEntity({required this.user, required this.course});
#override
List<Object> get props => [user, course];
}
MODELS:
UserModel:
class UserModel extends UserEntity {
const UserModel({
required int userID,
required String email,
}) : super(userID: userID, email: email);
factory UserModel.fromJson(Map<dynamic, dynamic> json) {
return UserModel(
userID: json["userID"],
email: json["email"],
);
}
Map<String, dynamic> toJson() {
return {
"userID": userID,
"email": email,
};
}
}
CourseModel:
class CourseModel extends CourseEntity {
const CourseModel({
required int courseID,
required String courseName,
}) : super(courseID: courseID, courseName: courseName);
factory CourseModel.fromJson(Map<dynamic, dynamic> json) {
return CourseModel(
courseID: json["courseID"],
courseName: json["courseName"],
);
}
Map<String, dynamic> toJson() {
return {
"courseID": courseID,
"courseName": courseName,
};
}
}
DataModel:
class DataModel extends DataEntity {
const DataModel({
required UserModel? user,
required CourseModel? course,
}) : super(user: user, course: course);
factory DataModel.fromJson(Map<dynamic, dynamic> json) {
return DataModel(
user: UserModel.fromJson(json["user"]),
course: CourseModel.fromJson(json["course"]),
);
}
}
LOCAL DATASOURCE:
Saving Data Locally:
abstract class LocalDataSource {
Future<void> saveData(DataModel model);
Future<void> saveUser(UserModel user);
Future<void> saveCourse(CourseModel course);
}
class LocalDataSourceImpl implements LocalDataSource {
#override
Future<void> saveData(DataModel model) async {
await saveUser(model.user);
await saveCourse(model.course);
}
#override
Future<void> saveUser(UserModel model) async {
final box = await Hive.openBox("USER");
return box.put("user", model.toJson());
}
#override
Future<void> saveCourse(CourseModel model) async {
final box = await Hive.openBox("COURSE");
return box.put("course", model.toJson());
}
}
Explanation:
I hope all the sample classes are easy to understand.
There are 2 entities User and Course. The Data entity is a combination of these 2 entities. The models are extending these entities which means that the entities are the parent classes for their respective models.
The class LocalDataSource is used to store data locally on the device. I'm using Hive to cache the data. There are 2 main futures namely saveUser and saveCourse. The saveData future is the combination of these 2 futures just like the entities.
Problem:
The problem exists in the future saveData of class LocalDataSourceImpl. The future requires 1 argument i.e. DataModel. The other futures also require 1 argument i.e. UserModel and CourseModel.
In saveData(model) future, when I provide the values for respective arguments to saveUser and saveCourse as saveUser(model.user) and saveCourse(model.course), this is the error that I receive on arguments passed:
The argument type 'UserEntity' can't be assigned to the parameter type 'UserModel'.dart(argument_type_not_assignable)
Workarounds:
First workaround could be using getters:
class DataModel extends DataEntity {
final UserModel? _user;
final CourseModel? _course;
const DataModel({
required UserModel user,
required CourseModel course,
}) : _user = user,
_course = course, super(
user: user,
course: course,
);
UserModel? get user => _user;
CourseModel? get course => _course;
...
}
Second workaround could be overriding fields:
class DataModel extends DataEntity {
final UserModel? user;
final CourseModel? course;
const DataModel({
required this.user,
required this.course,
}) : super(
user: user,
course: course,
);
...
}
I'm confused if any of these 2 is a good practice, because there must be no assignment error as Models are actually the subclasses of Entities.
Thanks in advance!
Simplest and Easiest way is to add type cast, and it works great!
class LocalDataSourceImpl implements LocalDataSource {
#override
Future<void> saveData(DataModel model) async {
await saveUser(model.user as UserModel);
await saveCourse(model.course as CourseModel);
}
...

Flutter & Shared Preferences : How do I save a list (of object) to local memory?

I want to save a list of objects into my local memory using the shared_preferences package.
Let's assume that this is my class:
class Person{
String name;
String age;
String gender;
}
How do I do that with shared_preferences?
I am looking forward to hearing from all of you. Thank you.
You can save a List<String> with shared_preferences.
Therefore, we need to convert the Person class, and its list, into a String by encoding them as JSON:
class Person {
String name;
String age;
String gender;
Person({this.name, this.age, this.gender});
factory Person.fromJson(Map<String, dynamic> parsedJson) {
return new Person(
name: parsedJson['name'] ?? "",
age: parsedJson['age'] ?? "",
gender: parsedJson['gender'] ?? "");
}
Map<String, dynamic> toJson() {
return {
"name": this.name,
"age": this.age,
"gender": this.gender,
};
}
}
void _savePersons(List<Person> persons) async {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
List<String> personsEncoded = persons.map((person) => jsonEncode(person.toJson())).toList();
await sharedPreferences.setStringList('accounts', accounts);
}
In the same fashion, we can get the saved List<Person> as a JSON object and de-serialize it:
List<Person> _getPersons(List<Person> persons) async {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
await sharedPreferences.setStringList('accounts', accounts);
return persons.map((person) => Person.fromJson(person)).toList();
}