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.
Second, setter injection is possible in an abstract class, but it's risky if we don't use the final keyword for the setter method. The application may not be stable if a subclass overrides the setter method.
You can't use setter injection in this case (again, because of final, it is Java, not because of Spring) Constructor injection, being in a general case better than setter injection can exactly handle your case.
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); //... } }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With