My Bloc state isn't updating and I've found the problem to potentially be a Map<String, Map<String, String> property that isnt being compared properly. Please correct me if I'm wrong, but the state updates when the other properties change, just not when the imageUrls property updates.
These are my state objects
abstract class PropertiesState extends Equatable {
const PropertiesState();
}
class PropertiesLoaded extends PropertiesState {
final int count;
final List<Property> properties;
final Map<String, Map<String, String>> imageUrls;
const PropertiesLoaded({
this.count,
this.properties,
this.imageUrls,
});
#override
List<Object> get props => [count, properties, imageUrls];
}
The imageUrls field can have any string key/value pairs.
I haven't been able to find any information on how I should do this.
Thanks for the help!
Because Equatable is trying to compare the reference of your List or Map object because they are not primitive type like int. You can try deep copy a Map object (using Map.from()) and replace the old one, and use hash code to do the comparison.
From Docs
Equatable properties should always be copied rather than modified. If
an Equatable class contains a List or Map as properties, be sure to
use List.from or Map.from respectively to ensure that equality is
evaluated based on the values of the properties rather than the
reference.
emit(PropertiesLoaded(count, properties,
Map<String, Map<String, String>>.from(imageUrls)));
Related
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
Im using bloc and it was working as expected but today i notice a strage behaviour when i was sending the same state (RefreshState) using copyWith, the state wasnt trigger after second call. then i did a test creating two objects and compared them but the result was they are the same object, very odd.
So why is this happen?, this is my class:
class Model extends Equatable {
final List<Product> mostBuyProducts;
const Model({
this.mostBuyProducts,
});
Model copyWith({
List<Product> mostBuyProducts,
}) =>
Model(
mostBuyProducts: mostBuyProducts ?? this.mostBuyProducts,
);
#override
List<Object> get props => [
mostBuyProducts,
];
}
and then i use the CopyWith method like (inside the bloc):
Stream<State> _onDeleteProduct(OnDeleteProduct event) async* {
state.model.mostBuyProducts.removeWhere((p) => p.id == event.id);
var newMostBuyProducts = List<Product>.from(state.model.mostBuyProducts);
final model1 = state.model;
final model2 = state.model.copyWith(mostBuyProducts: newMostBuyProducts);
final isEqual = (model1 == model2);
yield RefreshState(
state.model.copyWith(mostBuyProducts: newMostBuyProducts));
}
isEqual return true :/
BTW this is my state class
#immutable
abstract class State extends Equatable {
final Model model;
State(this.model);
#override
List<Object> get props => [model];
}
Yes because lists are mutable. In order to detect a change in the list you need to make a deep copy of the list. Some methods to make a deep copy are available here : https://www.kindacode.com/article/how-to-clone-a-list-or-map-in-dart-and-flutter/
Using one such method in the solution below! Just change the copyWith method with the one below.
Model copyWith({
List<Product> mostBuyProducts,
}) =>
Model(
mostBuyProducts: mostBuyProducts ?? [...this.mostBuyProducts],
);
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 []
Why do we need to use the Equatable class with flutter_bloc? Also, what do we use the props for? Below is sample code for making a state using the bloc pattern in Flutter.
abstract class LoginStates extends Equatable{}
class LoginInitialState extends LoginStates{
#override
List<Object> get props => [];
}
For comparison of data, we required Equatable. it overrides == and hashCode internally, which saves a lot of boilerplate code. In Bloc, we have to extend Equatable to States and Events classes to use this functionality.
abstract class LoginStates extends Equatable{}
So, that means LoginStates will not make duplicate calls and will not going to rebuild the widget if the same state occurs.
Define State:
class LoginInitialState extends LoginStates {}
Define State with props:
props declared when we want State to be compared against the values which declared inside props List
class LoginData extends LoginStates {
final bool status;
final String userName;
const LoginData({this.status, this.userName});
#override
List<Object> get props => [this.status, this.userName];
}
If we remove the username from the list and keep a list like [this.status], then State will only consider the status field, avoiding the username field. That is why we used props for handling State changes.
Bloc Stream Usage:
As we extending State with Equatable that makes a comparison of old state data with new state data. As an example let's look at the below example here LoginData will build a widget only once, which will avoid the second call as it is duplicated.
#override
Stream<LoginStates> mapEventToState(MyEvent event) async* {
yield LoginData(true, 'Hello User');
yield LoginData(true, 'Hello User'); // This will be avoided
}
Detail Blog: https://medium.com/flutterworld/flutter-equatable-its-use-inside-bloc-7d14f3b5479b
We are using the Equatable package so that we can compare instances of classes without having to manually override "==" and hashCode.
Equatable class allows us to compare two object for equality.
This is the equatable example. Let's say we have the following class:
class Person {
final String name;
const Person(this.name);
}
We can create instances of Person like so:
void main() {
final Person bob = Person("Bob");
}
Later if we try to compare two instances of Person either in our production code or in our tests we will run into a problem.
print(bob == Person("Bob")); // false
In order to be able to compare two instances of Person we need to change our class to override == and hashCode like so:
class Person {
final String name;
const Person(this.name);
#override
bool operator ==(Object other) =>
identical(this, other) ||
other is Person &&
runtimeType == other.runtimeType &&
name == other.name;
#override
int get hashCode => name.hashCode;
}
Now if we run the following code again:
print(bob == Person("Bob")); // true
it will be able to compare different instances of Person.
So you don't have to waste your time writing lots of boilerplate code when overrides "==" and hashCode.
Use Equatable like
class Person extends Equatable
In bloc case; if you try to use bloc with mutable state you will face with problems without Equatable. It makes resources immutable reduce performance. It’s more expensive to create copies than to mutate a property.
If it is not clear to you that I tried to explain, reading this could help you.
Equatable overrides == and hashCode for you so you don't have to waste your time writing lots of boilerplate code.
There are other packages that will actually generate the boilerplate for you; however, you still have to run the code generation step which is not ideal.
With Equatable there is no code generation needed and we can focus more on writing amazing applications and less on mundane tasks.
and the props is a getter of equatable that takes the properties that we want to
although it needs no focus on it i only putted properties to props getter
it is not that Important but i suggest you to read more about it in here
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