In Kotlin, I create generic classes to handle data fetching network states as following:
sealed class Resource<T>(
val data: T? = null,
val error: String? = null
) {
class Success<T>(data: T?) : Resource<T>(data)
class Loading<T> : Resource<T>()
class Error<T>(error: String) : Resource<T>(error = error)
}
Whenever I want to create an object of one of the available states, I can do that using Resource.Success<T> for example, not just Success<T>. I think this is a cleaner way to make my code easier to follow up with and understand.
I tried to achieve the same using dart, but I am stuck. I only created class the following way which is not really the same as what I want.
Can someone guide me on how to achieve my target using best practices?
abstract class ResourceState<T> {
T? data;
String? error;
ResourceState({this.data, this.error});
}
class ResourceLoading<T> extends ResourceState<T>{
ResourceLoading() : super();
}
class ResourceError extends ResourceState {
ResourceError(String error) : super(error: error);
}
class ResourceSuccess extends ResourceState {
ResourceSuccess(dynamic data) : super(data: data);
}
You will want to use named constructors:
class Resource<T> {
T ? data;
String? error;
Resource({this.data, this.error});
Resource.success(this.data);
Resource.error(this.error);
Resource.loading();
bool get isLoading => error == null && data == null;
bool get isSuccess => data != null;
bool get isError => error != null;
}
This allows you to create a success as Resource<T>.success(value) and an error as Resource<T>.error("Badness overwhelming!").
The type argument to the error constructor doesn't matter if you'll never add a data value. You could make it Resource<Never>.error("badness!").
I left the unnamed constructor public, in case you want to use it, but it's not necessary and can be removed.
If you want the class to really be sealed, you won't want any public generative (non-factory) constructors.
In that case, I'd do it as:
class Resource<T> {
T ? data;
String? error;
Resource._({this.data, this.error});
factory Resource.success(T data) => Resource._(data: data);
factory Resource.error(this.error) => Resource._(error: error);
factory Resource.loading() => Resource._();
// ...
}
This class cannot be extended because it has no public generative constructor available to do the super-constructor call that a subclass is require to do.
Its interface can still be implemented, because any Dart interface can.
Related
In one of my .net7-android project, I am trying to serialize an object using System.Text.Json. My object is derived from Java.Lang.Object. I am not intrested in serializing/deserializing the base class (Java.Lang.Object).
The exception I am getting is "Serialization and deserialization of 'System.Type' instances are not supported.". Anyone has any ideas how can it be fixed?
One of the public properties declared by Java.Lang.Object must (directly or indirectly) return an object of type System.Type, thereby causing the exception. Since you don't want to serialize any of these properties anyway, you could create a custom JsonTypeInfo modifier that excludes all properties declared by Java.Lang.Object.
First, define the following extension methods:
public static class JsonExtensions
{
public static Action<JsonTypeInfo> IgnorePropertiesDeclaredBy(Type declaringType)
=> (Action<JsonTypeInfo>) (typeInfo =>
{
if (typeInfo.Kind != JsonTypeInfoKind.Object || !declaringType.IsAssignableFrom(typeInfo.Type))
return;
foreach (var property in typeInfo.Properties)
if (property.GetDeclaringType() == declaringType)
property.ShouldSerialize = static (obj, value) => false;
});
public static Action<JsonTypeInfo> IgnorePropertiesDeclaredBy<TDeclaringType>() => IgnorePropertiesDeclaredBy(typeof(TDeclaringType));
public static Type? GetDeclaringType(this JsonPropertyInfo property) => (property.AttributeProvider as MemberInfo)?.DeclaringType;
}
And now you can use JsonExtensions.IgnorePropertiesDeclaredBy<Java.Lang.Object>() to omit all properties declared by Java.Lang.Object when serializing instances of derived types like so:
var options = new JsonSerializerOptions
{
TypeInfoResolver = new DefaultJsonTypeInfoResolver
{
Modifiers = { JsonExtensions.IgnorePropertiesDeclaredBy<Java.Lang.Object>() },
},
// Add other options as required
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = true,
};
var json = JsonSerializer.Serialize(myJavaObject, options);
Note that this will only suppress properties declared by some base type. Suppressing properties declared by an interface that the type implements is not implemented.
Demo fiddle using a mockup of Java.Lang.Object here: https://dotnetfiddle.net/8vNQS6.
I'm trying to have a base Freezed interface which my app entity interfaces can extend so I can call the freezed functions on the interfaces. I've started the process here which seems to be working so far:
abstract class IUserRegistrationEntity<T> extends FreezedClass<T> {
String get nickName;
String get email;
String get confirmEmail;
String get password;
String get confirmPassword;
}
abstract class FreezedClass<T> {
T get copyWith;
Map<String, dynamic> toJson();
}
freezed class:
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:vepo/domain/user_registration/i_user_registration_entity.dart';
part 'user_registration_entity.freezed.dart';
part 'user_registration_entity.g.dart';
#freezed
abstract class UserRegistrationEntity with _$UserRegistrationEntity {
#Implements.fromString(
'IUserRegistrationEntity<\$UserRegistrationEntityCopyWith<IUserRegistrationEntity>>')
const factory UserRegistrationEntity(
{String nickName,
String email,
String confirmEmail,
String password,
String confirmPassword}) = _IUserRegistrationEntity;
factory UserRegistrationEntity.fromJson(Map<String, dynamic> json) =>
_$UserRegistrationEntityFromJson(json);
}
But now I need to add the fromJson factory constructor to the interface. I think this may be what I'm looking for although I can't really tell how to implement it in my code:
T deserialize<T extends JsonSerializable>(
String json,
T factory(Map<String, dynamic> data),
) {
return factory(jsonDecode(json) as Map<String, dynamic>);
}
You an then call it with:
var myValue = deserialize(jsonString, (x) => MyClass.fromJson(x));
Any help adding the fromJson to my freezed interface would be appreciated.
I've found a way to get the same benefits of programming to an interface or "abstraction" with freezed objects, while still getting to call those freezed functions:
#freezed
abstract class Person with _$Person {
#With(BasicPersonMixin)
const factory Person.basicPerson(
{int? id, String? firstName, String? lastName}) = BasicPerson;
#With(FancyPersonMixin)
const factory Person.fancyPerson({String? firstName, required String extraPropMiddleName, String? lastName}) =
FancyPerson;
factory Person.fromJson(Map<String, dynamic> json) => _$PersonFromJson(json);
const Person._();
void functionThatEveryPersonShares() {
print('I am a person');
}
String greet() {
return 'override me with a mixin or abstract class';
}
}
mixin FancyPersonMixin {
String get extraPropMiddleName {
return 'my default middle name is John`;
}
String greet() {
return 'Salutations!';
}
void specialisedFunctionThatOnlyIHave() {
print('My middle name is $extraPropMiddleName');
}
}
mixin BasicPersonMixin {
String greet() {
return 'Hi.';
}
}
Now we have 2 concrete classes: BasicPerson, and FancyPerson which are both a Person. Now we can program to Person throughout the app, and still call .copyWith and .fromJson and so on and so forth. The different types of Person can vary independently from each other by using mixins and still be used as a Person type. Works with generics etc (from docs - #With.fromString('AdministrativeArea<House>')) but I have kept the example simple for this question to most simply show the benefits. You can also make Person extend another base class.
I've found another way to let you be a bit more abstract than my other answer. Say you're in a highly abstract super-class, so you don't want to work with objects as specific as Person. You want to work with "a base freezed object"; just cast your type to dynamic in brackets and go ahead and use copyWith freely. Sure, it's not typesafe, but it's a worthy option if it allows you to do something in a super-class rather than in every sub-class.
mixin LocalSaveMixin<TEntity extends LocalSaveMixin<TEntity>> on Entity {
LocalRepository<TEntity> get $repository;
Ref? get provider;
TEntity $localFetch() {
return ($repository.$localFetch() as dynamic).copyWith(provider: provider)
as TEntity;
}
TEntity $localSave() {
return $repository.$localSave(entity: this as TEntity);
}
}
How can I abstract that a methods has optional parameters?
abstract class CopyWith<T>{
T copyWith({}); // Error : Expected an identifier.
}
If I add an identifier like {test} it works and subclasses can have additional arguments
What I want to achieve?
I have a complex state manager, I make some abstraction , the following code is a minimal code, show my problem
import 'dart:collection';
abstract class CopyWith<T> {
T copyWith(OPTIONAL_NAMED_ARGUMENTS);
}
abstract class Manager<K, V extends CopyWith> {
final _map = HashMap<K, V>();
add(K key,V value){
_map[key] = value;
}
void copyWith(K key,OPTIONAL_NAMED_ARGUMENTS) {
assert(key != null);
if (_map.containsKey(key)) {
_map[key].copyWith(OPTIONAL_NAMED_ARGUMENTS);
}
}
}
class User implements CopyWith {
final int id;
final String name;
User({this.id, this.name});
User copyWith({int id, String name}) {
return User(
id: id ?? this.id,
name: name ?? this.name,
);
}
}
class UserManager extends Manager<int, User> {}
void main() {
final userManager = UserManager();
userManager.add(1,User(1,'test'));
userManager.copyWith(1,{test:'test2'})
}
As some one who has faced this issue in my library, I would say the only way is to not put a copyWith in your base class.
Why? Because you should only make a function polymorphic when there IS actually a shared calling convention and behavior. In your example, The way that these two classes perform copyWith is just different. It is, and should be, an error to send a name to Manager.copyWith, because Manager does not have a name to begin with. If you encounter a name inside a Manager.copyWith, that means there is some serious error in your code.
Also, if you actually try to invoke copyWith, as a responsible programmer, you will probably check if you are allowed to pass a name, which is,
if (someObj is User) {
someObj.copyWith(key, name: name);
} else if (someObj is Manager) {
throw IllegalStateError('You should not pass a name to a Manager! What am I supposed to do with the name now?');
}
There, you have already done type checking, so no need to make copyWith polymorphic.
However, some common behaviors can be made polymorphic, like updateKey. You can make Keyable as an interface, and Keyable updateKey(Key key) as an abstract method, and delegate to a non-polymorphic copyWith inside each subclasses.
Consider the following generic class:
class Foo<T extends Bar<dynamic>> {}
How can I obtain the generic argument of Bar at compile time?
I would expect the following code to print int, but it prints dynamic:
class Bar<T> {}
class Foo<T extends Bar<dynamic>> {
Foo(this.value) {
_debugBarType(value);
}
final T value;
void _debugBarType<A>(Bar<A> value) {
print(A);
}
}
void main() {
Foo<Bar<int>>(Bar());
}
I know I could do:
class Foo<A, Bar<A>> {}
But I would like to define the Foo class using a single generic parameter instead of two.
This is currently not possible to do on any class.
There's currently a pending feature request on Dart to support such use-case: https://github.com/dart-lang/language/issues/620
It's worth noting that there's a workaround if you can add a method on the class concerned.
For example, say you have a generic class Model<T>:
class Model<T> {
T value;
}
Then you can add a "capture" method as such:
class Model<T> {
T value;
R capture<T>(void cb<P>(P value)) => cb<T>(value);
}
This then allows you to write:
void someFunction<T extends Model<dynamic>>(T model) {
model.capture(<P>(value) {
// `P` will be the captured type instead of `dynamic`
});
}
A few times now I've run into a use case where I need to define an interface for how classes construct themselves. One such example could be if I want to make an Interface Class that defines the interface by which objects can serialize and unserialize themselves (for input into a database, to be sent as JSON, etc). You might write something like this:
abstract class Serializable {
String serialize();
Serializable unserialize(String serializedString);
}
But now you have a problem, as serialize() is properly an instance method, and unserialize() should instead be a static method (which isn't inheritable or enforced by the Interface) or a constructor (which also isn't inheritable).
This leaves a state where classes that impliment the Serializable interface are required to define a serialize() method, but there is no way to require those classes to define a static unserialize() method or Foo.fromSerializedString() constructor.
If you make unserialize() an instance method, then unserializing an implementing class Foo would look like:
Foo foo = new Foo();
foo = foo.unserialize(serializedString);
which is rather cumbersome and ugly.
The only other option I can think of is to add a comment in the Serializable interface asking nicely that implementing classes define the appropriate static method or constructor, but this is obviously prone to error if a developer misses it and also hurts code completion.
So, is there a better way to do this? Is there some pattern by which you can have an interface which forces implementing classes to define a way to construct themselves, or something that gives that general effect?
You will have to use instance methods if you want the inheritance guarantees. You can do a bit nicer than manual instantiation though, by using reflection.
abstract class Serializable {
static Serializable fromSerializedString(Type type, String serializedString) {
ClassMirror cm = reflectClass(type);
InstanceMirror im = cm.newInstance(const Symbol(''), []);
var obj = im.reflectee;
obj.unserialize(serializedString);
return obj;
}
String serialize();
void unserialize(String serializedString);
}
Now if someone implements Serializable they will be forced to provide an unserialize method:
class Foo implements Serializable {
#override
String serialize() {
// TODO: implement serialize
}
#override
void unserialize(String string) {
// TODO: implement unserialize
}
}
You can get an instance like so:
var foo = Serializable.fromSerializedString(Foo, 'someSerializedString');
This might be a bit prettier and natural than the manual method, but keep in mind that it uses reflection with all the problems that can entail.
If you decide to go with a static method and a warning comment instead, it might be helpful to also provide a custom Transformer that scans through all classes implementing Serializable and warn the user or stops the build if any don't have a corresponding static unserialize method or constructor (similar to how Polymer does things). This obviously wouldn't provide the instant feedback the an editor could with instance methods, but would be more visible than a simple comment in the docs.
I think this example is a more Dart-like way to implement the encoding and decoding. In practice I don't think "enforcing" the decode signature will actually help catch bugs, or improve code quality. If you need to make the decoder types pluggable then you can make the decoders map configurable.
const Map<String,Function> _decoders = const {
'foo': Foo.decode,
'bar': Bar.decode
};
Object decode(String s) {
var obj = JSON.decode(s);
var decoder = _decoders[obj['type']];
return decoder(s);
}
abstract class Encodable {
abstract String encode();
}
class Foo implements Encodable {
encode() { .. }
static Foo decode(String s) { .. }
}
class Bar implements Encodable {
encode() { .. }
static Foo decode(String s) { .. }
}
main() {
var foo = decode('{"type": "foo", "i": 42}');
var bar = decode('{"type": "bar", "k": 43}');
}
A possible pattern I've come up with is to create a Factory class that utilize instance methods in a slightly less awkward way. Something like follows:
typedef Constructable ConstructorFunction();
abstract class Constructable {
ConstructorFunction constructor;
}
abstract class Serializable {
String serialize();
Serializable unserialize(String serializedString);
}
abstract class SerializableModel implements Serializable, Constructable {
}
abstract class ModelFactory extends Model {
factory ModelFactory(ConstructorFunction constructor) {
return constructor();
}
factory ModelFactory.fromSerializedString(ConstructorFunction constructor, String serializedString) {
Serializable object = constructor();
return object.unserialize(serializedString);
}
}
and finally a concrete implementation:
class Foo extends SerializableModel {
//required by Constructable interface
ConstructorFunction constructor = () => new Foo();
//required by Serializable interface
String serialize() => "I'm a serialized string!";
Foo unserialize(String serializedString) {
Foo foo = new Foo();
//do unserialization work here to populate foo
return foo;
};
}
and now Foo (or anything that extends SerializableModel can be constructed with
Foo foo = new ModelFactory.fromSerializedString(Foo.constructor, serializedString);
The result of all this is that it enforces that every concrete class has a method which can create a new instance of itself from a serialized string, and there is also a common interface which allows that method to be called from a static context. It's still creating an extra object whose whole purpose is to switch from static to instance context, and then is thrown away, and there is a lot of other overhead as well, but at least all that ugliness is hidden from the user. Still, I'm not yet convinced that this is at all the best way to achieve this.
I suggest you define the unserialize function as named constructor like so:
abstract class Serializable<T> {
String serialize();
Serializable.unserialize(String serializedString);
}
This eliminates the need of static methods.
A possible implementation could look like this:
import 'dart:convert';
class JsonMap implements Serializable<JsonMap> {
Map map = {};
JsonMap() {
}
String serialize() {
return JSON.encode(map);
}
JsonMap.unserialize(String serializedString) {
this.map = JSON.decode(serializedString);
}
}
You can (de)serialize like so:
JsonMap m = new JsonMap();
m.map = { 'test': 1 };
print(m.serialize());
JsonMap n = new JsonMap.unserialize('{"hello": 1}');
print(n.map);
While testing this, I noticed that Dart will not throw any errors at you if you dont actually implement the methods that your class promises to implement with implements. This might just be a hicc-up with my local Dart, though.