I have models that I need to map them to JSON and vice versa. Therefore, I've followed flutter's JSON and serialization guide on how to do so.
I found myself writing the same code base for each model, like so:
import 'package:flutter/material.dart';
import 'package:json_annotation/json_annotation.dart';
part 'folder_entity.g.dart';
#JsonSerializable(explicitToJson: true)
class FolderEntity {
final String id;
final String path;
bool isSelected;
FolderEntity({
#required this.id,
#required this.path,
this.isSelected = false,
});
factory FolderEntity.fromMap(Map<String, dynamic> json) => _$FolderEntityFromJson(json);
Map<String, dynamic> toMap() => _$FolderEntityToJson(this);
}
I thought about moving fromMap and toMap to a new abstract class called Entity, but I'm unable to do so for two reasons:
I can't extend a factory, therefore I have write the same pattern for each method.
I'm not sure if there's an option to change prefix from _$FolderEntity to just _$Entity, and if would still be available, will it even work?
For second question you should be change prefix from _$FolderEntity to just _$Entity in folder_entity.g.dart file.
Related
I have just started learning freezed. I have a GameBase class below:
import 'package:json_annotation/json_annotation.dart';
part 'game_base.g.dart';
#JsonSerializable()
class GameBase {
final int id;
final String facilityName;
final ActivityType activityType;
final Level level;
final DateTime startTime;
final DateTime endTime;
final int participantsNumber;
final String admin;
const GameBase(
{required this.id,
required this.level,
required this.activityType,
required this.admin,
required this.startTime,
required this.facilityName,
required this.endTime,
required this.participantsNumber});
factory GameBase.fromJson(Map<String, dynamic> json) =>
_$GameBaseFromJson(json);
}
Now i have another class called Game, which extends from GameBase. I'm trying to use freezed on this class. I also have a getter in this class. Game class is shown bellow:
part 'game.freezed.dart';
part 'game.g.dart';
#freezed
class Game extends GameBase with _$Game {
Game._();
factory Game({
required List<UserBase> participants,
required String? gameDescription,
required String? activityGroundsName,
required DateTime day,
required double lat,
required double lng,
required int id,
required Level level,
required ActivityType activityType,
required String admin,
required DateTime startTime,
required String facilityName,
required DateTime endTime,
required int participantsNumber,
}) = _Game;
factory Game.fromJson(Map<String, dynamic> json) => _$GameFromJson(json);
get facilityActivityText {
if (activityGroundsName == null) {
return facilityName;
} else {
return facilityName + " - " + activityGroundsName!;
}
}
}
since I have a getter in this class, I must have a private custructor, as mentioned in freezed documentation. However, by doing so I get an error since I extend from GameBase and must call its costructor with its fields.
*Note: I know I can move one field up to GameBase and then have my getter over there without any issue, but since I've just started working with freezed I want to understand it better and find out if there's any way to handle this?
I think your problem's same as this thread. Hope this thread will answer ur questions.
Flutter/Dart: Subclass a freezed data class
in the flutter when you are defining a model. the convention is to define properties as final and write a copyWith for class instead of defining non-final vars and removing the copyWith method. what is the exact reason for this? is it a flutter performance thing?
for example:
class Emplyee {
final String name;
final String id;
Emplyee({required this.name, required this.id});
Emplyee copyWith({String? name, String? id}) {
return Emplyee(id: id ?? this.id, name: name ?? this.name);
}
Map<String, dynamic> toJson() => {
"name": name,
"id": id,
};
Emplyee.fromJson(Map<String, dynamic> json)
: name = json["name"],
id = json["id"];
}
P.S. I know this convention makes sense in widgets. but my question is about data model classes.
It's for Immutability. Mutable class is error-prone.
So basically the copyWith method makes it possible for you to create a new instance of the class and then you can edit whatever you want in this new instance of the class, without affecting data in the original class.
So that's what the copyWith method does, I don't think it's for performance, I think it just aids some coding use cases.
Immutability reduces the risk of errors by side effects. Have a look at this code:
class User {
String name;
User({required this.name});
}
void main() {
final user = User(name: 'Stefan');
someFunction(user);
print(user.name);
}
someFunction(User user){
user.name = 'Thomas';
}
This snippet prints 'Thomas' because the function manipulates the user object. In the main function, you have no chance to know what happens with the object.
With immutability, this would not be possible. It would be necessary to create a new instance of User to have a User named 'Thomas'. The instance in the main function would be the same.
Using final makes a class immutable.
You can check Why do we need immutable class?
Immutable class is good for caching purpose and it is thread safe.
The reason behind using copyWith() is flexible, allowing any number of properties to be updated in a single call.
More about immutable-data-patterns-in-dart-and-flutter
This is my Hive model and I already generate the adapter:
#HiveType(typeId: 7)
class GoalModel {
#HiveField(0)
bool progessNotificaitons;
#HiveField(1)
List<DayModel> daysToWork;
#HiveField(2)
int amountGoal;
#HiveField(3)
DateTime initialDate;
GoalModel({
this.amountGoal,
this.daysToWork,
this.initialDate,
this.progessNotificaitons,
});
}
class DayModel {
String label;
int index;
bool gonnaWork;
DayModel({
this.gonnaWork,
this.index,
this.label,
});
}
The issue is that does not allow me save the daysToWork which is a list of the class DayModel which does not have an adapter. the question is: is required generate an adapter or any special config to save a list of a type of object?
Thanks in advance.
The response is YES, is required generate an register a new adapter even if you just are going to issue the object into a list of another object.
I want to set a default value for AvailableService. It straight forward enough with primitives. How would I do it with a custom Object
class Submenu extends Equatable {
#JsonKey(defaultValue: "")
final String name;
#JsonKey(defaultValue: new AvailableService(false,false,false,false))
final AvailableService availableService;
}
the custom Object:
AvailableService {
bool supportsDelivery;
bool supportsTableService;
bool supportsCollection;
bool supportsChat;
}
And the compile time error is
Arguments of a constant creation must be constant expressions.
Try making the argument a valid constant, or use 'new' to call the constructor
In general, as an annotation argument you can only pass constants, so you cannot pass an object created with new but only with a const constructor (you should define all AvailableService fields as final, and define a const constructor).
In json_Serializable, however, defaultValue currently has some additional constraints:
You can't even use a custom object created with a const constructor (or you get the error "it must be a literal"). There is an open request to remove this constraint: #741 - Const as default value
As defaultValue only literals are allowed; you could then pass a Map literal (ie: #JsonKey(defaultValue: {"supportsDelivery" : false, "supportsTableService" : false, /* etc */})), but even this possibility is currently only usable for the default value of fields with Map type, and not for fields with custom types (like AvailableService). In this case, you not get an error, but the defaultValue will never be used (by looking at the code generated in the .g.dart file, you can understand why). There are open requests for this issue as well: #676 - Allow default value encoded as JSON and #789 - how to set default value to nested JSON objects?
At the moment, therefore, until the aforementioned requests are followed up, the only workaround I have found is handle the defaultValue in the fromJson() factory:
factory AvailableService.fromJson(Map<String, dynamic> json) =>
json != null
? _$AvailableServiceFromJson(json)
: AvailableService(false, false, false, false);
In short, JsonKey.defaultValue currently only supports literals -not even accepting constants- and can only be used for the default value of primitive type fields (or List, Map, Set).
Starting from json_serializable 5, generated fromJson uses default parameters from constructor, so you can do this
#JsonSerializable()
class Cart {
final List<int> items;
const Cart({
this.items = const [],
});
factory Cart.fromJson(Map<String, dynamic> json) => _$CartFromJson(json);
Map<String, dynamic> toJson() => _$CartToJson(this);
}
#JsonSerializable()
class User {
final Cart cart;
const User({
this.cart = const Cart(),
});
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
Map<String, dynamic> toJson() => _$UserToJson(this);
}
Then Cart.fromJson({}).items or User.fromJson({}).cart.items will give you []
In my code, I have the Step class:
class Step{
String name;
bool completed;
Step({
#required this.name,
#required this.completed
});
Map<String, dynamic> toMap() => {
'name': name,
'completed': completed
};
}
I use Firebase as my backend and pull data from cloud firestore using StreamBuilder and pass it to my StepView widget:
StreamBuilder(
stream: Firestore.instance.collection('step').snapshots,
builder: (context, snapshot) {
sample_step = snapshot.data.documents[0].data;
return StepView(
step: sample_step
)
}
)
My question is: What is the better practice between two options:
Passing the raw data I pulled from cloud Firestone, which is a Map<String, dynamic>, to my widget:
class StepView extends StatefulWidget {
final Map<String, dynamic> step;
}
Or converting the Map to the Step class and pass that instance of Step class to my widget:
// Convert the Map<String, dynamic> sample_test to class Step
// Need to code the method fromMapToClass in Step class
new_step = Step.fromMapToClass(sample_step)
return StepView(
step: new_step
)
class StepView extends StatefulWidget {
final Step step;
}
That totally depends on how you prefer to code your widgets and which state management scheme you would use.
If I was at your place and I used ProviderModel or any other model for state management:
I would CONVERT the map to class object FIRST
You need to implement a method fromMapToClass in your class to be able to convert the firestore data to the required Step class object.
Your Step class should look somthing like this (the function could change depending upon you data)
class Step {
String name;
bool completed;
Step({#required this.name, #required this.completed});
Map<String, dynamic> toMap() => {'name': name, 'completed': completed};
Step.fromMapToClass(Map<String, dynamic> json) {
this.name = json['name']??"";
this.completed = json['completed']??false;
}
}
Then send the Step class object to my Widget Class (or StateManagement class which in ProviderModel case will be the Provider class that extends ChangeNotifier).
This way your widget will not have to handle the conversion part and can just focus on the design and implementation.