23

Consider a MVP-ish set of types. An abstract Presenter exists, with a View interface:

public interface View {
    //...
}

public abstract class AbstractPresenter<V extends View> {
    @Inject V view;
    //...
}

Then, lets have a specific concrete presenter subclass, with its view interface and implementation:

public interface LoginView extends View {
    //...
}
public LoginPresenter extends AbstractPresenter<LoginView> {
    //...
}

public class LoginViewImpl implements LoginView {
    //...
}

In a Dagger module, of course we would define a @Provides method:

@Provides
LoginView provideLoginView() {
    return new LoginViewImpl();
}

In Guice you could write this the same way, or just bind(LoginView.class).to(LoginViewImpl.class).

However, in Dagger (both v1 and the 2.0-SNAPSHOT from Google), this produces an error, since it can't figure out what V is when creating the binding wiring for AbstractPresenter<V>. On the other hand, Guice figures out that that because it is actually creating a LoginPresenter, so it needs an implementation of LoginView.

Dagger 1.2.2:

foo.bar.AbstractPresenter$$InjectAdapter.java:[21,31] cannot find symbol
  symbol:   class V
  location: class foo.bar.AbstractPresenter$$InjectAdapter

Dagger 2.0-SNAPSHOT:

Caused by: java.lang.IllegalArgumentException: V
    at dagger.internal.codegen.writer.TypeNames$2.defaultAction(TypeNames.java:39)
    at dagger.internal.codegen.writer.TypeNames$2.defaultAction(TypeNames.java:36)
    at javax.lang.model.util.SimpleTypeVisitor6.visitTypeVariable(SimpleTypeVisitor6.java:179)
    at com.sun.tools.javac.code.Type$TypeVar.accept(Type.java:1052)
    at dagger.internal.codegen.writer.TypeNames.forTypeMirror(TypeNames.java:36)
    at dagger.internal.codegen.MembersInjectorGenerator.write(MembersInjectorGenerator.java:142)
    at dagger.internal.codegen.MembersInjectorGenerator.write(MembersInjectorGenerator.java:61)
    at dagger.internal.codegen.SourceFileGenerator.generate(SourceFileGenerator.java:53)
    at dagger.internal.codegen.InjectBindingRegistry.generateSourcesForRequiredBindings(InjectBindingRegistry.java:101)
    at dagger.internal.codegen.ComponentProcessor.process(ComponentProcessor.java:149)

My question: Is this a bug? Is this a missing feature? Or is this a performance issue that Dagger is protecting us from (a la SerializableTypeOracleBuilder in GWT RPC)?

Note that this same issue occurs when V is referred to as Provider<V>, Lazy<V>, etc.

Colin Alworth
  • 17,801
  • 2
  • 26
  • 39
  • 1
    As far as I know this (extremely useful IMO) Guice feature isn't intended to be supported in Dagger. But it wouldn't be impossible to implement either. I'd ask on their [mailing list](https://groups.google.com/forum/#!forum/dagger-discuss). – Tavian Barnes Oct 20 '14 at 14:35
  • Since Dagger is a code generation based DI framework, how is it supposed to wire up the object graph based on generics? Guice is reflection based, meaning at runtime it can figure all that out. – toadzky Feb 25 '15 at 07:01
  • @toadzky on the one hand, that's true, but on the other remember that generics are erased at runtime - by the same argument how can reflection figure it out? I'm not suggesting that the abstract class have T injected, but that the concrete subclass with `V` specified as `LoginView` no longer is generic, and with the field's type able to be resolved, dagger should inject the superclass's fields. – Colin Alworth Feb 25 '15 at 15:14
  • it's not erased as fully as you would think at runtime. guice4 uses typeliterals to capture that stuff and figure it out. that's why you can't ask an injector for a list like this: `injector.getInstance(List.class)`. You have to do it like this: `injector.getInstance(new TypeLiteral>(){})` so that it can figure out what type of list you actually want. – toadzky Feb 25 '15 at 16:55
  • I'm not asking for magic, I'm looking for the annotation processor's generics inference to notice that LoginPresenter subclasses Presenter and makes V into LoginView (which is generally solvable in other code generation tools). The TypeLiteral is *created* at runtime its true, but the anon subclass in your code (the `{}` afterward) is statically defined as a subclass of TypeLiteral with T resolved to List, so can be looked up. – Colin Alworth Feb 25 '15 at 20:19
  • I'd have to look at it closer but I'd say it should be possible. Possibly just a missing call to [`asMemberOf`](http://docs.oracle.com/javase/7/docs/api/javax/lang/model/util/Types.html#asMemberOf%28javax.lang.model.type.DeclaredType,%20javax.lang.model.element.Element%29) – Thomas Broyer Apr 30 '15 at 17:47
  • Might have been fixed by https://github.com/google/dagger/commit/2ea676a2702a48866f227988a9994e0e6649e1ce – Thomas Broyer Apr 30 '15 at 18:26
  • Thanks @ThomasBroyer, looking forward to trying again with a little free time... – Colin Alworth Apr 30 '15 at 18:43
  • @ColinAlworth According to this thread it might work now: https://github.com/google/dagger/issues/181#issuecomment-97896987 –  Apr 30 '15 at 23:11

2 Answers2

1

That looks like a bug as it shouldn't throw an exception, but it should log a warning explaining that type parameters need to be bound to a specific type.

The rest is for Dagger2, and I'm using 2.1-SNAPSHOT. You haven't provided an example @Component that will do the injection and without it Dagger2 2.1-SNAPSHOT doesn't actually report a problem. It's possible that it has already fixed your problem and I'm seeing a slightly different version but if not then I presume your component looks something like this:

@Component
public interface PresenterComponent {
  <V extends View> void inject(AbstractPresenter<V> presenter);
}

When Dagger2 is processing this it cannot determine a concrete type for V, and so it doesn't know what type to insert. It can't just insert say LoginView because that would break if it was passed a AbstractPresenter<LogoutView>.

However, if you use say the following then Dagger2 can determine that it needs to inject a LoginView into AbstractPresenter<LoginView> and will do so safely.

@Module
public class LoginModule {
  @Provides LoginView provideLoginView() {
    return new LoginViewImpl();
  }
}

@Component(modules = LoginModule.class)
public interface LoginComponent {
    void inject(LoginPresenter presenter);
}

Unless you have no control over when an object is created, e.g. if some framework creates it for you and then passes in for you to initialize, it is much better to use @Inject on the constructor if you can, e.g. like this:

public LoginPresenter extends AbstractPresenter<LoginView> {
    //...
    @Inject LoginPresenter(LoginView view) {
        super(view);
    //...
    }
}
Paul Duffin
  • 201
  • 1
  • 4
  • No `inject` style method existed in my code for the abstract class - the `LoginPresenter` was created in a `Provider` field or `@Injecte`d into a parent class. This was tested as of 2.0-snapshot, and I have not had the chance to re-rebuild all of the guice-based code to attempt in Dagger2 since then. – Colin Alworth Oct 07 '15 at 20:38
0

This is because of the Type arguments. Injects does not work when u have a type arguments. U need to do something like this,

bind(new LoginPresenter<LoginViewImpl>(){});
Keaz
  • 955
  • 1
  • 11
  • 21
  • sorry u have to use com.google.inject.TypeLiteral https://github.com/google/guice/wiki/FrequentlyAskedQuestions#how-to-inject-class-with-generic-type – Keaz Oct 07 '15 at 04:38
  • I was trying to point you to the fact that the question is not about guice :) – zapl Oct 07 '15 at 11:38
  • More importantly, LoginPresenter isn't generic, it extends a generic superclass and specifies the generic parameter: `LoginPresenter extends AbstractPresenter`. Even if Dagger did bindings like guice, this code would not compile. – Colin Alworth Oct 07 '15 at 13:48