shuriquen
shuriquen

Reputation: 899

Creating an instance of a generic type in DART

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
    }
}

Upvotes: 75

Views: 58224

Answers (9)

Allen Hu
Allen Hu

Reputation: 655

2023 answer

In addition to the following code, newer versions of dart allows you to instantiate without even passing the generic type, and the code becomes much more cleaner:

final myClass = MyClass(SomeOtherClass.new);

Thanks @AndriiSyrokomskyi for pointing this out.

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.

Upvotes: 16

Karlay Costa
Karlay Costa

Reputation: 1

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});
}

Upvotes: -1

James Poulose
James Poulose

Reputation: 3833

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.

Upvotes: -1

Patrick Cornelissen
Patrick Cornelissen

Reputation: 7958

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.

Upvotes: 106

Teenenggr
Teenenggr

Reputation: 91

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

Upvotes: 2

Hemil
Hemil

Reputation: 1016

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

Upvotes: 12

Anton Duzenko
Anton Duzenko

Reputation: 2596

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();
}

Upvotes: 7

mezoni
mezoni

Reputation: 11210

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'.");
    }
  }
}

Upvotes: 14

yorodm
yorodm

Reputation: 4461

Sorry but as far as I know, a type parameter cannot be used to name a constructor in an instance creation expression in Dart.

Upvotes: 4

Related Questions