I want to parse http response to Dart object,so I defined a abstract class
BaseBean:
abstract class BaseBean{
BaseBean.fromJson(Map<String, dynamic> json);
Map<String, dynamic> toJson();
}
And I used it in a function:
Future<ResultData<T>> netFetch<T extends BaseBean>(){
......
return new ResultData(T.fromJson(), result, code);
}
but T.fromJson() has an error:
The method 'fromJson' isn't defined for the class 'Type'
So,can I use generics in Dart like this?Is there a better way to solve this problem?
Yes, of course, it is possible, but only with a workaround:
T unmarshal<T>(Map map, {Type type}) {
if (type == null) {
type = T;
}
switch (type) {
case Order:
return Order.fromJson(map) as T;
case OrderItem:
return OrderItem.fromJson(map) as T;
case Product:
return Product.fromJson(map) as T;
default:
throw StateError('Unable to unmarshal value of type \'$type\'');
}
}
var order = unmarshal<Order>(data);
//
var product = unmarshal(data, type: Product) as Product;
//
var type = <String, Type>{};
types['OrderItem'] = OrderItem;
// ...
var type = types['OrderItem'];
var orderItem = unmarshal(data, type: type);
I used the same approach and I came to this solution.
I created a Map<Type, BaseBean Function(dynamic)> decoders.
It looks like
final decoders = {
MyClass: (data) => MyClass.fromJson(data),
};
and I get the function in this way
final MyClass myData = decoders[T]!(data);
You can also create a typedef for the BaseBean Function(dynamic) like this
typedef Decoder = BaseBean Function(dynamic);
So the Map<Type, BaseBean Function(dynamic)> will became Map<Type, Decoder>.
Related
In Dart, I can dynamically call a function using Function.apply:
Function.apply(foo, [1,2,3], {#f: 4, #g: 5});
gives exactly the same result as
foo(1, 2, 3, f: 4, g: 5).
Question: Does a similar thing exist for instantiating classes?
Expected result would look something like:
class foo {
final String boo;
int? moo;
foo({required this.boo, this.moo})
}
...
var params = {boo: 'A string value', moo: 121};
Class.apply(foo, params);
// Gives the result:
foo(boo: 'A string value', moo: 121);
Function.apply isn't type-safe, so you should avoid using it if you can.
If you really want to use it with a constructor, you can use it with constructor tear-offs (added in Dart 2.15), which are just Functions:
class Foo {
final String boo;
int? moo;
Foo({required this.boo, this.moo});
#override
String toString() => 'Foo(boo: "$boo", moo: $moo)';
}
void main() {
var params = {#boo: 'A string value', #moo: 121};
var result = Function.apply(Foo.new, [], params);
print(result); // Prints: Foo(boo: "A string value", moo: 121)
}
As far as I know, you can make use of static methods if you want to create an instance without using another instance. Here is a sample:
class Foo {
final String boo;
final int moo;
Foo({this.boo, this.moo});
static fromValues({String boo, int moo}) {
return Foo(boo: boo, moo: moo);
}
}
void main() {
var params = {#boo: 'A string value', #moo: 121};
var fooObject = Function.apply(Foo.fromValues, [], params);
print(fooObject);
print(fooObject.boo);
print(fooObject.moo);
}
Another way is to add 'call' function to class to make it's objects callable and use an object of the class to create new objects. Here is a sample:
class Foo {
final String boo;
final int moo;
Foo({this.boo, this.moo});
call({String boo, int moo}) {
return Foo(boo: boo, moo: moo);
}
}
void main() {
Foo aFoo = Foo(boo: 'nothing', moo: 0);
var params = {#boo: 'A string value', #moo: 121};
var fooObject = Function.apply(aFoo, [], params);
print(fooObject);
print(fooObject.boo);
print(fooObject.moo);
}
I'm trying to specify a function parameter as a generic type T:
enum MyEnum {
Foo,
Bar
}
class DbColumn {
final Function<T>(T value) serializer;
const DbColumn({this.serializer});
}
class MyClass {
static final DbColumn rating = DbColumn(
serializer: <MyEnum>(v) {
var i = v.index;
}
);
}
However when trying to access index on v I get this type error message:
The getter 'index' isn't defined for the type 'Object'.
Try importing the library that defines 'index', correcting the name to the name of an existing getter, or defining a getter or field named 'index'.
When I hover over v in VSC it says that it's of type MyEnum.
If I instead remove the generic type and do a cast like this it works as expected:
class DbColumn {
final Function(dynamic value) serializer;
const DbColumn({this.serializer});
}
class MyClass {
static final DbColumn rating = DbColumn(
serializer: (v) {
var casted = v as MyEnum;
var i = casted.index;
}
);
}
Why is the generic type not working as expected?
EDIT:
What is even weirder is that this example works too if I put it inside MyClass:
x<T>(Function(T) c) {}
y() {
x<MyEnum>((v) {
print(v.index); // No error given and type of v is MyEnum
});
}
EDIT 2: The same problem happens when overriding methods:
abstract class MyInterface {
int someFunction<T>(T value);
}
class MyClass implements MyInterface {
#override
someFunction<MyEnum>(v) {
return v.index; // Gives same error and no intellisense happens in VSC
}
}
Instead of making the function generic, declare the class as generic and it will work as expected. Like this :
enum MyEnum {
Foo,
Bar
}
class DbColumn<T> {
final Function(T value) serializer;
const DbColumn({this.serializer});
}
class MyClass {
static final DbColumn<MyEnum> rating = DbColumn(
serializer: (v) {
var i = v.index;
print('Index : $i');
}
);
}
void main() {
MyClass.rating.serializer(MyEnum.Bar);
}
OUTPUT :
Index : 1
With the below code as an example I can not figure out how to make the generic typed Function work with out casting as shown. Every other way I try I get some variation of
The argument type 'Null Function(Gift)' can't be assigned to the
parameter type 'dynamic Function(T)'
var present = Present<Gift>(Gift('Fancy Gift'), <T>(Gift t) {
print('${(t as Gift).name} was opened.');
});
or
The getter 'name' isn't defined for the type 'Object'
var present = Present<Gift>(Gift('Fancy Gift'), <Gift>(t) {
print('${t.name} was opened.');
});
Here is the working example with a cast.
void main() {
var present = Present<Gift>(Gift('Fancy Gift'), <T>(t) {
print('${(t as Gift).name} was opened.');
});
present.open();
}
class Present<T> {
final T _item;
final Function<T>(T t) openedCallback;
T open() {
openedCallback.call(_item);
return _item;
}
Present(this._item, this.openedCallback);
}
class Gift {
final String name;
Gift(this.name);
}
There should be a way to do this without a cast right?
Your class definition does not do what you intend:
class Present<T> {
final T _item;
final Function<T>(T t) openedCallback;
...
openedCallback is separately parameterized; its T type parameter is separate and independent from that of Present<T>. There is no need to parameterize openedCallback since you presumably want:
class Present<T> {
final T _item;
final Function(T t) openedCallback;
...
After that, you can do:
var present = Present<Gift>(Gift('Fancy Gift'), (t) {
print('${t.name} was opened.');
});
Note that doing <T>(t) { ... } or <Gift>(t) { ... } is counterproductive. That declares an anonymous function that itself is generic and is has a type parameter named T or Gift respectively.
I was wondering if is possible to create an instance of a generic type in Dart. In other languages like Java you could work around this using reflection, but I'm not sure if this is possible in Dart.
I have this class:
class GenericController <T extends RequestHandler> {
void processRequest() {
T t = new T(); // ERROR
}
}
I tried mezonis approach with the Activator and it works. But it is an expensive approach as it uses mirrors, which requires you to use "mirrorsUsed" if you don't want to have a 2-4MB js file.
This morning I had the idea to use a generic typedef as generator and thus get rid of reflection:
You define a method type like this: (Add params if necessary)
typedef S ItemCreator<S>();
or even better:
typedef ItemCreator<S> = S Function();
Then in the class that needs to create the new instances:
class PagedListData<T>{
...
ItemCreator<T> creator;
PagedListData(ItemCreator<T> this.creator) {
}
void performMagic() {
T item = creator();
...
}
}
Then you can instantiate the PagedList like this:
PagedListData<UserListItem> users
= new PagedListData<UserListItem>(()=> new UserListItem());
You don't lose the advantage of using generic because at declaration time you need to provide the target class anyway, so defining the creator method doesn't hurt.
You can use similar code:
import "dart:mirrors";
void main() {
var controller = new GenericController<Foo>();
controller.processRequest();
}
class GenericController<T extends RequestHandler> {
void processRequest() {
//T t = new T();
T t = Activator.createInstance(T);
t.tellAboutHimself();
}
}
class Foo extends RequestHandler {
void tellAboutHimself() {
print("Hello, I am 'Foo'");
}
}
abstract class RequestHandler {
void tellAboutHimself();
}
class Activator {
static createInstance(Type type, [Symbol constructor, List
arguments, Map<Symbol, dynamic> namedArguments]) {
if (type == null) {
throw new ArgumentError("type: $type");
}
if (constructor == null) {
constructor = const Symbol("");
}
if (arguments == null) {
arguments = const [];
}
var typeMirror = reflectType(type);
if (typeMirror is ClassMirror) {
return typeMirror.newInstance(constructor, arguments,
namedArguments).reflectee;
} else {
throw new ArgumentError("Cannot create the instance of the type '$type'.");
}
}
}
I don't know if this is still useful to anyone. But I have found an easy workaround. In the function you want to initialize the type T, pass an extra argument of type T Function(). This function should return an instance of T. Now whenever you want to create object of T, call the function.
class foo<T> {
void foo(T Function() creator) {
final t = creator();
// use t
}
}
P.S. inspired by Patrick's answer
2022 answer
Just came across this problem and found out that although instantiating using T() is still not possible, you can get the constructor of an object easier with SomeClass.new in dart>=2.15.
So what you could do is:
class MyClass<T> {
final T Function() creator;
MyClass(this.creator);
T getGenericInstance() {
return creator();
}
}
and when using it:
final myClass = MyClass<SomeOtherClass>(SomeOtherClass.new)
Nothing different but looks cleaner imo.
Here's my work around for this sad limitation
class RequestHandler {
static final _constructors = {
RequestHandler: () => RequestHandler(),
RequestHandler2: () => RequestHandler2(),
};
static RequestHandler create(Type type) {
return _constructors[type]();
}
}
class RequestHandler2 extends RequestHandler {}
class GenericController<T extends RequestHandler> {
void processRequest() {
//T t = new T(); // ERROR
T t = RequestHandler.create(T);
}
}
test() {
final controller = GenericController<RequestHandler2>();
controller.processRequest();
}
Sorry but as far as I know, a type parameter cannot be used to name a constructor in an instance creation expression in Dart.
Working with FLutter
typedef S ItemCreator<S>();
mixin SharedExtension<T> {
T getSPData(ItemCreator<T> creator) async {
return creator();
}
}
Abc a = sharedObj.getSPData(()=> Abc());
P.S. inspired by Patrick
simple like that.
import 'dart:mirrors';
void main(List<String> args) {
final a = A<B>();
final b1 = a.getInstance();
final b2 = a.getInstance();
print('${b1.value}|${b1.text}|${b1.hashCode}');
print('${b2.value}|${b2.text}|${b2.hashCode}');
}
class A<T extends B> {
static int count = 0;
T getInstance() {
return reflectClass(T).newInstance(
Symbol(''),
['Text ${++count}'],
{Symbol('value'): count},
).reflectee;
}
}
class B {
final int value;
final String text;
B(this.text, {required this.value});
}
Inspired by Patrick's answer, this is the factory I ended up with.
class ServiceFactory<T> {
static final Map<Type, dynamic> _cache = <String, dynamic>{};
static T getInstance<T>(T Function() creator) {
String typeName = T.toString();
return _cache.putIfAbsent(typeName, () => creator());
}
}
Then I would use it like this.
final authClient = ServiceFactory.getInstance<AuthenticationClient>(() => AuthenticationClient());
Warning: Erik made a very good point in the comment below that the same type name can exist in multiple packages and that will cause issues. As much as I dislike to force the user to pass in a string key (that way it's the consumer's responsibility to ensuring the uniqueness of the type name), that might be the only way.
Say I have
class RestSimulator {
#Path("/var")
void functionOne() {
final Type type = this.runtimeType;
final InstanceMirror instanceMirror = reflect(this);
final ClassMirror classMirror = instanceMirror.type;
final MethodMirror methodMirror = ?????
var metadata = methodMirror.metadata;
var path = metadata.first.reflectee;
print(path.toString()):
}
}
How can I get the MethodMirror for the calling function???
[Update]
I meant without doing something like
final MethodMirror methodMirror = functions[const Symbol('functionOne')];
So probably the main question is: How do I get the Symbol for the calling / current function?
AFAIK there's no simple way to get a reference on the current function at runtime.
I say simple because you can get the name from a StackTrace but it's really ugly and has horrible performances...
class A {
m() {
var functionName;
try {
throw '';
} catch(e, s) {
functionName = parseStackTraceToGetMethod(s.toString());
}
print(functionName); // displays A.m
}
}
parseStackTraceToGetMethod(String s) =>
s.substring(8, s.indexOf("("));
main() {
new A().m();
}