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.
Related
I have a class B extends class A, and it have the toJson method override the class A's toJson method. But when i force class B to class A, the toJson method not be changed from class B to class A.
class A {
String str1;
A(this.str1);
Map<String, dynamic> toMap() => {
'str1': str1,
}
}
class B extends A {
String str2;
B(this.str2, str1) : super(str1);
Map<String, dynamic> toMap() => {
'str2': str2,
'str1': str1,
}
}
void test(A a) {
print(a.toMap());
}
// When I pass class B in class A parameter, it still use the class B.toMap method.
B b = B('str2', 'str1');
test(b);
All Dart instance methods are virtual, also known has having dynamic dispatch. That means that when you call a.toMap(), which toMap implementation gets called depends on the runtime type of the object a, not the static type of the expression a.
So, in this case, you are calling toMap on an instance of B, so you use B's toMap.
This is a fairly common behavior in object oriented languages (dynamic dispatch is one of the hallmark features of object orientation: The object determines how to respond to the message you send it, here the request to do a "toMap" operation.)
Some object oriented languages also have non-virtual methods. Dart does not.
You can either define a static method:
class A {
static Map<String, dynamic> toMap(A value) => ...;
}
class B {
static Map<String, dynamic> toMap(B value) => ...;
}
and then choose which one to call:
print(A.toMap(a));
or you can use extension methods (which are really just static methods with a nicer call syntax) because they dispatch based on the static type of the receiver:
extension AToMap on A {
Map<String, dynamic> toMap() => ...;
}
extension BToMap on B {
Map<String, dynamic> toMap() => ...;
}
and call it normally:
print(a.toMap()); // Chooses AToMap.toMap based on static type of `a`.
In most situations, if you really want to access both A.toMap and B.toMap on an instance of B, you're better of renaming one of the methods to avoid the naming conflict.
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 using the following dart packages (json_annotation, json_serializable, build_runner) to serialize/de-serialize json according to this page.
This is my code:
import 'package:json_annotation/json_annotation.dart';
part 'car_type.g.dart';
#JsonSerializable()
class CarType {
final int id;
#JsonKey(name: 'type_label')
final String label;
final String description;
CarType(this.id, this.label, this.description);
factory CarType.fromJson(Map<String, dynamic> json) =>
_$CarTypeFromJson(json);
factory List<CarType> CarType.fromJsonList(dynamic jsonArray){
final list = jsonArray as List;
final carTypesList = list.map((i) => CarType.fromJson(i));
return carTypesList;
}
}
So with the factory List<CarType> CarType.fromJsonList(dynamic jsonArray) I want to pass a json array to get back a list of CarType objects. However I'am getting a couple of compiler errors namely:
This function has a return type of 'List', but doesn't end with a return statement.dart(missing_return)
The default constructor is already defined.dart(duplicate_constructor_default)
Constructors can't have type parameters.dart(type_parameter_on_constructor)
Any idea what is going on?
factory List<CarType> CarType.fromJsonList(dynamic jsonArray){
You can't specify a return type for a constructor.
The return type is always the same as the class the constructor is member of.
Just replace factory with static and you should be fine,
except json_serializable expects a factory constructor, then you need to remove the return type and find another approach to get the List.
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.
I am porting some Java-code to Dart and it heavily uses these kinds of maps:
Map<Class<? extends SomeClass>, SomeOtherClass> map = new HashMap<>();
At the moment this seems to be impossible in dart. I am aware that there is a proposal to introduce first level types: http://news.dartlang.org/2012/06/proposal-for-first-class-types-in-dart.html which would introduce
class Type {
#native String toString();
String descriptor(){...} // return the simple name of the type
}
So until this proposal gets implemented I have created following class:
class Type {
final String classname;
const Type(this.classname);
String descriptor() => classname;
}
and the classes where I need it have a simple get-method
abstract Type get type();
That way I can use my Type just like I would use the real Type and to switch later I'd just have to delete my workaround.
My question: Is there some dart-way of doing this kind of mapping (which I am not seeing) or is the way I do it a reasonable workaround until the real Type class gets introduced?
Update for Dart 1.0
It can be done this way:
var map = new Map<Type, SomeOtherClass>();
// either
map[SomeOtherClass] = new SomeOtherClass();
// or
var instance = new SomeOtherClass();
map[instance.runtimeType] = instance;
Update: this construction is not currently doable in Dart
Map<Class<? extends SomeClass>, SomeOtherClass>
you will have to wait for .type/.class to arrive for an elegant solution to this (lots of us Dartisans are hoping that this will arrive sooner rather than later). However for the simpler case
Map<? extends SomeClass, SomeOtherClass>
You can just do
Map<SomeClass, SomeOtherClass> aMap;
as in Dart any class that extends SomeClass is also going to be a valid SomeClass. For example if you run the following code in checked mode:
main() {
Map<Test, String> aMap = new HashMap<Test, String>();
var test = new Test("hello");
var someTest = new SomeTest("world");
var notATest = new NotATest();
aMap[test] = test.msg;
aMap[someTest] = someTest.msg;
aMap[notATest] = "this fails";
}
class Test implements Hashable {
Test(this.msg);
int hashCode() => msg.hashCode();
final String msg;
}
class SomeTest extends Test {
SomeTest(String message): super(message);
}
class NotATest implements Hashable {
int hashCode() => 1;
}
then you you will get the error:
type 'NotATest' is not a subtype of type 'Test' of 'key'.