76

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
    }
}
3
  • 2
    Type parameters in Dart implements the interface Type. The Type interface does not declares any members. This means that the interface Type used only as the identity key of runtime types. The reflection procedures built into Dart SDK but they are not a part of Dart core. This means that if you want to introspect your program you should use reflection library. Bridge between your program (at runtime) and reflection library are the interface Type. You request (reflect) required information about the classes using this interface. Commented Apr 16, 2014 at 15:53
  • 1
    See also github.com/dart-lang/sdk/issues/12921 Commented Jun 4, 2015 at 14:29
  • Not exact solution but, this may work for you; void processRequest(T t) { t.something(); } Commented Feb 20, 2022 at 12:35

9 Answers 9

106

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.

Sign up to request clarification or add additional context in comments.

8 Comments

Works with flutter
it totally worked for me. But I don't understand how creator() is creating an instance of T and not an instance of ItemCreator<T>. Can you explain that for me?
ItemCreator<T> is a function type of a function that returns something of type T, as you can see in the typedef. In the last code snippet you can see that the PagedListData instance is created and an instance of the creator function is provided. This is just a template and not a function call. This function is only called in the middle code snippet at "creator()". Does that help?
There really is no magic here. It boils down to delegating the instantiation responsibility to the consumer, via a callback. It's not a bad solution, but it would have been nice to be able to dynamically instantiate a type inside PagedListData without it. But I don't think it's possible without mirrors, which we don't have in Flutter.
As a matter of taste you might want your PagedListData and creator() declared abstract so that they are implemented on the sub-class level rather than by client.
|
16

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.

5 Comments

This does not work at all.. There is so such things as .new 👎
@Dinesh Idk what you're saying, literally just works by pasting directly to dartpad.dev
sorry my bad. Looks there is .new but it does not create the instance of the SomeOtherClass. Unless I am missing something.
It's already possible to write shorter: final myClass = MyClass(SomeOtherClass.new)
@AndriiSyrokomskyi You're right, thanks for notifying the new language changes. Updated my answer.
14

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

4 Comments

Won't work with flutter yet. +1 for Patrik's approach below.
Mirror is not supported on many targets (Flutter, js). Solutions using it are very limited.
@mezoni I did this exactly because of the passage of time. I was looking for a solution for a small project in Flutter and in many sources I found mirrors recommended without any reservations about their current status and targeting issues. This comment is simply to help someone who stumbles upon this thread. You could edit the post yourself and add an explanation. This is really helpful when you get into the ecosystem.
But, getting back to the point, for some reason it seems to me that the problem is not the answer, which is no longer relevant. The problem is that the world is changing and sometimes there simply may not be universal answers. It is also quite possible that it is not possible to provide an answer “for all times” and we will be content with what we currently have at our disposal. But, it seems to me, it’s somehow not very correct to hang labels decades later.
12

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

Comments

7

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

Comments

4

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

Comments

2

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

1 Comment

Why is the function async btw?
-1

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.

3 Comments

Hi James, note that it is error-prone and unnecessary to transform the Type instance to a string (apparently, a comment will not contain code, so please reformat): /* Library 'lib.dart'. */ class C {} /* Library 'main.dart'. */ import 'lib.dart' as lib; class C {} void main() { print('${(C).toString()} == ${((lib.C).toString())}'); } The point is that two different libraries may declare a class with the same name, and they will have the same toString, but they are not the same class.
@ErikErnst You make a very good point and I have edited the answer to include a warning to that effect, thanks!.
Thanks! Actually, you would just use Map<Type, dynamic> _cache and _cache.putIfAbsent(T, () => creator());.
-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});
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.