I have an Identity class in dart which looks (simplified) like this
class Identity {
final String phoneNumber;
Identity({#required this.phoneNumber});
Identity.fromJson(Map<String, dynamic> json)
: phoneNumber = json['phoneNumber'];
}
I will use this class to send an http POST to my identity service; this service will return a json which I want to map to ActiveIdentity, which looks like this (also simplified).
class ActiveIdentity extends Identity {
final String id;
ActiveIdentity.fromJson(Map<String, dynamic> json)
: id = json['id'];
}
Now, is there a way to extend or call fromJson in Identity so that I can "extend" this method? Ideally when calling fromJson in ActiveIdentity I should receive a new ActiveIdentity instance with all properties inititalized (phoneNumber and id) but on ActiveIdentity I only want to deal with id.
I also tried to think about this in terms of mixins, but failed miserably... any idea on how would be the best way to achieve this?
Thanks!
I think the following should solve your problem:
class Identity {
final String phoneNumber;
Identity({#required this.phoneNumber});
Identity.fromJson(Map<String, dynamic> json)
: phoneNumber = json['phoneNumber'];
}
class ActiveIdentity extends Identity {
final String id;
ActiveIdentity.fromJson(Map<String, dynamic> json)
: id = json['id'],
super.fromJson(json) {
print('$phoneNumber $id');
}
}
Try having a look at the Dart documentation of constructors for clarification on this topic.
Related
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);
}
}
I am trying to create a base class for my models but I am struggling with the error The name 'cls' isn't a type so it can't be used as a type argument.. So, how can I pass the object's constructor to the Hive.box method?
import 'package:hive/hive.dart';
class AppModel {
#HiveField(0)
int id;
#HiveField(1)
DateTime createdAt;
#HiveField(2)
DateTime updatedAt;
save() async {
final Type cls = this.runtimeType;
// The name 'cls' isn't a type so it can't be used as a type argument.
final Box box = await Hive.openBox<cls>(cls.toString());
await box.put(this.id, this);
return this;
}
}
#HiveType(typeId: 0)
class UserModel extends AppModel {
#HiveField(3)
String email;
#HiveField(4)
String displayName;
}
void main() {
final UserModel user = UserModel()
..email = 'user#domain.com'
..displayName = 'john doe';
user.save().then(() {
print('saved');
});
}
Dart does not have a way to refer to the dynamic type of this (a "self type").
The way such things are often handled is to have a self-type as type argument, so:
class AppModel<T extends AppModel> {
save() async {
final Box box = await Hive.openBox<T>(T.toString());
await box.put(this.id, this as T);
return this;
}
...
and then ensure that each subclass tells the superclass what type it is:
class UserModel extends AppModel<UserModel> {
...
}
(or, if you expect to subclass UserModel eventually:
class UserModel<T extends UserModel> extends AppModel<T> {
...
}
so that a subclass can still pass its type through).
You are also talking about constructors, and for that there is no easy solution.
Dart's type parameters are types, not classes. You cannot access static members or constructors from a type variable, and there is also no other way to pass a class around.
The only way you can have something call a constructor that it doesn't refer to statically, is to wrap the constructor call in a function and pass that function.
(I can't see how you need the constructor here).
I have a problem with JSON convert in child class in dart. For example:
class Person {
String firstName;
String lastName;
Person({
this.firstName,
this.lastName
})
factory Person.fromJson(Map<String, dynamic> json){
return Person()
..firstName = json['firstName']
..lastName = json['firstName'];
}
}
class User extends Person {
String token;
User({
this.token
});
factory User.fromJson(Map<String, dynamic> json){
return User()
..token = json['token']
// The problem is here. I need dublicate code to fill parent
// class properties as first & last name of person:
..firstName = json['firstName']
..lastName = json['firstName'];
}
}
My question is how to avoid code dublication in this case?
Please help.
You could create a method on your base class that takes in the Map<String, dynamic> and populates the fields on itself, then call that from the subclass. However, I'd really recommend using something like json_serializable to generate this code for you instead. It adds an additional step (you need to run pub run build_runner build or pub run build_runner watch to have the code re-generated) but it avoids you having to hand-write all of this.
i have no experience with extended classes
so don't be shocked... that's what I got:
the 'basic class' I want to extend in my models
to avoid repeat fromJson/toJson every 2 lines
import 'dart:convert';
class BaseModel {
Map<String, dynamic> json2Map(String json) => jsonDecode(json);
String map2Json(Map<String, dynamic> map) => jsonEncode(map);
json2List(String jsonList) {
List<Map<String, dynamic>> _list = [];
jsonDecode(jsonList).forEach((_json) => _list.add(jsonDecode(_json)));
return _list;
}
mapList2Json(List<Map<String,dynamic>> list) {
List<String> jsonList= [];
list.forEach((_map) => jsonList.add(map2Json(_map)));
return jsonEncode(jsonList);
}
}
and here is one of the class that extends this:
import 'package:bloc_hub/models/base_model.dart';
class Info extends BaseModel {
final String name;
final String company;
Info({this.name,this.company});
factory Info.fromMap(Map<String, dynamic> json) => new Info(
name: json['name'],
company: json['company'],
);
Map<String, dynamic> toMap() {
var map = new Map<String, dynamic>();
map['name'] = name;
map['company'] = company;
return map;
}
}
(I'm in a streambuilder and client.info is a json)
then... when I try to call 'json2map'
which is from the extended class...
Info info = Info.fromMap(json2Map(client.info));
i get this:
[dart] The method 'json2Map' isn't defined for the class 'ListPage'. [undefined_method]
what did I get wrong?
if I wasn't clear don't refrain to ask me anything
thank you for your help
[edit: bonus question
how a mixin is different from what I'm doing?]
json2Map is an instance method of BaseModel, so in order to call it you must use an instance of BaseModel or a class that extends it (like Info), like:
var b = new BaseModel();
b.json2Map(something);
The error message says you're calling it from ListPage, so the method is not found.
Alternatively, you could make the methods static and call it like BaseModel.json2Map(...) (without an instance).
There are some good explanations about mixins here (with Python examples, but the concepts are the same for Dart). I guess in your example it would make more sense to have a mixin with JSON related functions, since you could use them in other kind of objects.
I'm currently trying to create a GlobalExtension for my Geb-Spock framework. So far here is my extension:
class OnFailureListener extends AbstractRunListener {
private final String id
private final SauceREST sauceREST
public OnFailureListener(String id, String username, String accessKey) {
this.id = id
this.sauceREST = new SauceREST(username, accessKey)
}
def void error(ErrorInfo error) {
println error;
this.sauceREST.updateJobInfo(this.sessionIdProvider.getSessionId(), "failed")
}
}
class ResultExtension extends AbstractGlobalExtension {
protected final String username = System.getenv("SAUCE_USERNAME")
protected final String accesskey = System.getenv("SAUCE_ACCESS_KEY")
protected final String sessionId
#Override
void visitSpec(SpecInfo specInfo) {
specInfo.addListener(new OnFailureListener(sessionId, username, accesskey))
}
}
My issue is that the sesssionId value gets assigned in the GebSpec base class I'm using for other specs, and cannot be assigned directly in the extension class. Beyond using some gnarly reflection approaches, is there a way to access the sessionId value assigned in the base class in the extension? I'd also like to avoid using an AnnotationExtension since I'd like to apply this globally without modifying any spec code (similar to a JUnit TestWatcher pattern).
The easiest way for you would be to write the sessionId into a shared ThreadLocal that can be accessed by your listener and the spec, otherwise you'll have to implement an org.spockframework.runtime.extension.IMethodInterceptor so that you can gain access to the actual test instance to extract the field value.