google/dagger

Allow multibinding on classes using @Inject constructors

6bangs opened this issue ยท 4 comments

Most of my classes are constructed by Dagger using @Inject constructors rather than with @Provides methods to cut down on boilerplate. But multibinding annotations can't be applied to classes, so I can't get a nice plugin architecture without resorting to @Provides methods. For example, this is what I would like to do:

@IntoMap
@StringKey("pluginA")
public class PluginA implements Plugin {
  @Inject
  public PluginA() {
    // ...
  }
}

@IntoMap
@StringKey("pluginB")
public class PluginB implements Plugin {
  @Inject
  public PluginB() {
    // ...
  }
}

public class C {
  @Inject
  public C(Map<String, Plugin> plugins) {
    // Do stuff with the plugins
  }
}

I can see one reason this wouldn't work: Dagger can't know what value class to use when constructing the map. Maybe we would need to introduce another annotation for that, i.e. @ValueClass(Plugin.class).

How would this work if you wanted to multibind PluginB into both Map<String, Plugin> and @Named("named") Map<String, Plugin>? Once you get anything beyond the start (regular, singular binding from @Inject`, this starts to get tricky. And doing classpath scanning for multibindings can be very dirty when you want to have different configurations of your app receiving different configurations of the multibindings.

I'm not sure I understand the first question. Isn't it the same with multibindings as they are defined currently? If you want to multibind PluginB into two maps, you need two Provides methods defined in your modules. With multibinding on the class, you can multibind PluginB into one map on the class, and if you want to multibind into a second map, you can declare it in a module.

It's the same with single binding case: I can annotate scopes or qualifiers on the class, but if I want to provide the dependency in another context I can always add a Provides method with different scopes, qualifiers, etc.

As for classpath scanning, again, I'm not sure I follow, but my gut feeling is that the problem is analogous to the single binding case (if you want to provide a different binding for a different configuration of your app, you have to use modules instead of annotations on the class).

Unfortunately, this request isn't feasible.

Starting from the component definition, Dagger follows static, explicit information to form the object graph. While they don't appear in modules, this applies to @Inject constructors as well as they are only included in the graph whenever the Dagger processor encounters a request (e.g. an argument to a @Provides method) for the @Injected type.

For multibindings, the problem is that the binding itself and all requests for it are of a different type from that which declares the binding. In your example, the requests are in terms of Plugin, but the bindings are on PluginA and PluginB.

So, in order to implement such a feature, when it generates the component the Dagger processor would have to see a request for Plugin and then scan the compile-time classpath for all implementations of Plugin to see if any has a multibinding annotation. This is impractical for even moderately sized builds.

FYI I released a library that does this for you using Hilt (Android only unfortunately unless Hilt starts supporting non Android projects): https://auto-dagger.ansman.se/