Flutter: When should the factory constructor be used? - flutter

https://flutter.dev/docs/cookbook/networking/fetch-data
In the last 'complete example' of the above page,
class Album {
final int userId;
final int id;
final String title;
Album({this.userId, this.id, this.title});
factory Album.fromJson(Map<String, dynamic> json) {
return Album(
userId: json['userId'],
id: json['id'],
title: json['title'],
);
}
}
It is an Album class to receive the JSON string received in the request and handle it in the application,
The constructor provides a factory constructor in addition to the normal constructor.
About the factory constructor,
https://dart.dev/guides/language/language-tour#constructors
I have read the Factory constructors section of the above page.
The factory constructor of the Logger class in the sample does not always create a new instance, so
I can understand adding the factory keyword,
Is it necessary to use the factory constructor even in the Album class of this Complete example?
In the case of the Album class, since the normal constructor is used in the factory constructor,
I feel that this factory constructor (Album.fromJson) always creates a new instance.
In fact
Future<Album> fetchAlbum() async {
final response =
await http.get('https://jsonplaceholder.typicode.com/albums/16');
if (response.statusCode == 200) {
// If the server did return a 200 OK response,
// then parse the JSON.
var temp=json.decode(response.body);
return Album(userId:temp['userId'],id:temp['id'],title:temp['title']);
} else {
// If the server did not return a 200 OK response,
// then throw an exception.
throw Exception('Failed to load album');
}
}
As you can see, it seems that it works without any problems even if I try using only the normal constructor.
Is there any advantage to preparing and using a factory constructor?
Or is there something wrong without using the factory constructor in this situation?
I'm not sure when to use the factory constructor in the first place,
Is there a clear definition?

Before diving into factory as keyword in flutter, you might have a look at Factory as design pattern to have the full picture in mind.
The main benefit of using factory design pattern
That is, the Factory Method design pattern defines an interface for a class responsible for creating an object, hence deferring the instantiation to specific classes implementing this interface. This resolves the issue of creating objects directly within the class which uses them.
Also, it enables compile-time flexibility via subclassing. When objects are created within the class, it is very inflexible since you cannot change the instantiation of the object independently from the class — the class is committed to a particular object. By implementing the pattern, subclasses can be written to redefine the way an object is created.
For more info, see here
and as docs refers
Use the factory keyword when implementing a constructor that doesn’t always create a new instance of its class. For example, a factory constructor might return an instance from a cache, or it might return an instance of a subtype. Another use case for factory constructors is initializing a final variable using logic that can’t be handled in the initializer list.
So it's all about hiding the creation logic from outside world.
And of course you can do the following
return Album(userId:temp['userId'],id:temp['id'],title:temp['title']);
But if you did that in many different components or classes let's say, so whenever you change the logic behind the creation of Album object you will be in-need to change it over all places.
On the other hand the classes which use Album class they only care about having an Object of Album, they care not about how it got instantiated, so if you put the logic of having an instance outside the class itself you are going into what's called spaghetti code

You can use factory to test for example if the json data returned by som request is null so you return a null dirctly by the use of factory named constructor for example look at this
//class for Product, Brand, Model
class PBM {
static const String pbmCollectionName = 'productsBrandsModels';
static const String pbmIdField = 'pbmId';
static const String pbmNameField = 'pbmName';
static const String parentIdField = 'parentId';
static const String iconUrlField = 'iconUrl';
//general
final String pbmId;
final String pbmName;
final String parentId;
//icon
final String iconUrl;
PBM({
this.pbmId,
this.pbmName,
this.parentId,
this.iconUrl,
});
Map<String, dynamic> toMap() {
return {
'pbmId': pbmId,
'pbmName': pbmName,
'parentId': parentId,
'iconUrl': iconUrl,
};
} //end of toMap method
factory PBM.fromFirestore(Map<String, dynamic> firestore) {
//here the benefit of factory comes into play: it will return a null
//otherwise it gonna create the object
if (firestore == null) return null;
return PBM(
pbmId: firestore['pbmId'],
pbmName: firestore['pbmName'],
parentId: firestore['parentId'],
iconUrl: firestore['iconUrl'],
);
} //end of PBM.fromFirestore named constructor
} //end of PBM class

Related

Dart json serializable - how to return a const IconData

Storing the IconData on the server and retrieving it with the JsonSerializable, lead to the following error on the Jenkin android build.
This application cannot tree shake icons fonts. It has non-constant instances of IconData at the following locations:
The following code dto to build the icon data object:
#JsonSerializable(explicitToJson: true)
class DrawerIconDataDto extends IconData {
const DrawerIconDataDto(
super.codePoint, {
super.fontFamily,
super.fontPackage,
super.matchTextDirection,
});
/// Create a new instance from a json.
factory DrawerIconDataDto.fromJson(Map<String, dynamic> json) {
return _$DrawerIconDataDtoFromJson(json);
}
/// Convert this instance to a json.
#override
Map<String, dynamic> toJson() => _$DrawerIconDataDtoToJson(this);
}
Even adding const to the following class lead to the error. I tried to add a const to the factory but it is not possible.
How can I get a constant DataIcon object from the server using JsonSerializable and without modifying the Jenkins configuration (not allowed).
In Dart, const means that the compiler can determine the value at compile time. Obviously, this does not apply to data retrieved from a server.
So, you cannot retrieve const data from a server.
What you could do is have all possible IconData values as const instances in you code, and retrieve an identifier from the server.

final model properties in flutter

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

Should I use multiple classes for bloc states or one class with multiple constructors?

I have started learning bloc recently and I noticed some tutorials use multiple classes for states and others just use one class with multiple named constructors.
Example for multiple classes:
abstract class ProductsState {}
class ProductsInitial extends ProductsState {}
class ProductsLoading extends ProductsState {}
class ProductsSuccess extends ProductsState {
final List<Products> products;
ProductsSuccess(this.products);
}
class ProductsError extends ProductsState {
final String error;
ProductsError(this.error);
}
Example for multiple constructors:
enum AuthenticationStates {
initial,
success,
error,
}
class AuthenticationState {
final AuthenticationStates state;
final Authentication model;
final String msg;
const AuthenticationState._(
{this.state = AuthenticationStates.initial,
this.model,
this.msg = ''});
const AuthenticationState.initial() : this._();
const AuthenticationState.success(Authentication mdl)
: this._(model: mdl, state: AuthenticationStates.success);
const AuthenticationState.error(String m)
: this._(state: AuthenticationStates.error, msg: m);
}
Which one is better to use?
In my projects, I use the first way since different state types are clear, you can later build your UI based on that e.g. if (state is ProductsLoading) { show loader }.
You can also generate such classes by using the freezed package (https://pub.dev/packages/freezed). When using it, you would define a single class with different factory constructors (similar to your second option), but the generated code would support the first option you've defined, e.g.:
#freezed
class Union with _$Union {
const factory Union(int value) = Data;
const factory Union.loading() = Loading;
const factory Union.error([String message]) = ErrorDetails;
}
Later, in the UI layer, you could use helper methods like map/maybeMap/when/maybeWhen to build the corresponding view based on state, e.g.:
var union = Union(42);
print(
union.when(
(int value) => 'Data $data',
loading: () => 'loading',
error: (String? message) => 'Error: $message',
),
); // Data 42
I was asking myself the same question.
"The moment you check the concrete type of an instance in your logic, know you're not using polymorphism correctly." - Back in the university.
In the documentation of BloC they use one class with enum in all examples(or extra status class),
the enum will be composed of something similar to SUCCESS FAILURE etc.
Check the PostState mentioned in BloC documentation as example:
https://bloclibrary.dev/#/flutterinfinitelisttutorial?id=post-states
Nevertheless, the multiple sub-classes for state is a commonly used practice among flutter devs. It looks wrong for me, but who knows...

How to set a default value for an object in json serialisable?

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 []

How to define proper state class in bloc while using freezed plugin

I am trying to understand, how can I use the bloc pattern (specifically state) properly. I am facing this issue for more than a month, but not able to figure out a solution.
Let's consider, I have defined state class following way,
#freezed
abstract class ExampleState with _$ExampleState {
const factory ExampleState.initial() = Initial;
const factory ExampleState.getDataFromServer() = GetDataFromServer; //Thsi will return the ServerData Object.
const factory ExampleState.useServerData() = UseServerData;
const factory ExampleState.changeServerDataAndUpload() = ChangeServerDataAndUpload;
}
Let's consider our Server Data Model is the following way
class ServerData {
final String userId;
final String firstName;
final String lastName;
final String fullAddress;
final String fatherName;
ServerData(
this.userId,
this.firstName,
this.lastName,
this.fullAddress,
this.fatherName,
);
}
In this example, we are able to see, GetDataFromServer, UseServerData, and ChangeServerDataAndUpload state is sharing the same ServerData object. How should I design my state such that, the same DataModel object can be shared between different states?