46

lets say I have:

public interface Shape  {}


public class Rectangle implements Shape {

}

public class Circle implements Shape {

}

and I have a ApplicationModule which needs to provides instances for both Rec and Circle:

@Module
public class ApplicationModule {
    private Shape rec;
    private Shape circle;

    public ApplicationModule() {
        rec = new Rectangle();
        circle= new Circle ();
    }

    @Provides
    public Shape provideRectangle() {
        return rec ;
    }

    @Provides
    public Shape provideCircle() {
        return circle;
    }
}

and ApplicationComponent:

@Component(modules = ApplicationModule.class)
public interface ApplicationComponent {
    Shape provideRectangle();
}

with the code the way it is - it won't compile. error saying

Error:(33, 20) error: Shape is bound multiple times.

It makes sense to me that this can't be done, because the component is trying to find a Shape instance, and it finds two of them, so it doesn't know which one to return.

My question is - how can I handle this issue?

Ofek Agmon
  • 5,040
  • 14
  • 57
  • 101
  • 1
    You can use `@Qualifier` to distinguish between different types. [Here](http://frogermcs.github.io/dependency-injection-with-dagger-2-the-api/) is a short example of how to use this annotation. Also [this SO question](http://stackoverflow.com/questions/35829544/dagger2-custom-qualifier-usage) maybe useful. – QBrute Oct 10 '16 at 11:35
  • 1
    @ofek-agmon when you are using di concepts dont use new in Contructor definition instead provide and method that provides new Object and these object would get injected in the respective constructors. – silentsudo Oct 10 '16 at 11:56
  • thanks for your answers. @sector11 can you point me to an sample code to see what you mean? – Ofek Agmon Oct 10 '16 at 13:13

4 Answers4

65

I recently post the answer to a question like this in this post :

Dagger 2 : error while getting a multiple instances of same object with @Named

You need to use @Named("someName")in your module like this:

@Module
public class ApplicationModule {
private Shape rec;
private Shape circle;

public ApplicationModule() {
    rec = new Rectangle();
    circle= new Circle ();
}

@Provides
 @Named("rect")
public Shape provideRectangle() {
    return rec ;
}

@Provides
 @Named("circle")
public Shape provideCircle() {
    return circle;
}

}

Then wherever you need to inject them just write

@Inject
@Named("rect")
 Shape objRect;

its funny but you have to inject in a different way in Kotlin:

@field:[Inject Named("rect")]
lateinit var objRect: Shape
Amir Ziarati
  • 14,248
  • 11
  • 47
  • 52
  • So apparently this code compiles fine, but the injection actually doesn't work. I opened a new question about it here, will appreciate if you took a look. http://stackoverflow.com/q/40139428/1662033 – Ofek Agmon Oct 19 '16 at 18:56
  • The Kotlin part did the trick. The code was not compiling when I was applying the annotation in the traditional way. – Devansh Maurya Jul 28 '23 at 12:59
16

@Qualifier annotations are the right way to distinguish different instances or injection requests that have the same type. The main User's Guide page has a whole section on them.

@Qualifier @Retention(RUNTIME)
public interface Parallelogram {} /* name is up to you */

// In your Module:
@Provides @Parallelogram
public Shape provideRectangle() {
    return rec ;
}

// In your other injected types:
@Inject @Parallelogram Shape parallelogramShape;
// or
@Inject @Parallelogram Provider<Shape> parallelogramShapeProvider;

// In your Component:
@Parallelogram Shape provideRectangle();

Aside: Though I agree with sector11 that you shouldn't use new in injected types, Modules are exactly the correct place to call new if needed. Aside from adding the qualifier annotations, I'd say your Module looks just right to me.


EDIT regarding the use of @Named compared to custom qualifier annotations:

  • @Named is a built-in @Qualifier annotation, much like the one I've created above. For simple cases, it works great, but because the binding is just a string you won't get as much help from your IDE in detecting valid keys or autocompleting the key.
  • Like with Named's string parameter, custom qualifiers can have string, primitive, enum, or class literal properties. For enums, IDEs can often autocomplete valid values.
  • @Named and custom qualifiers can be accessed from annotations in exactly the same way by specifying the annotation on the component method, as I've done with @Parallelogram above.
Jeff Bowman
  • 90,959
  • 16
  • 217
  • 251
  • thanks for your answer. I will read about the qualifier annotations, but isn't Amir Ziarati's answer good also? I just added now to @Named("rect") and it works great, but I thhink it only works when you inject using the @Inject annotation, and not working when creating a provide method in component like this: `Shape provideCircle();`, `Shape provideRect();` and then you can do: `Circle = myComponent.provideCircle();`. I think the @Named("rect") will have a problem is this case. what do you think? – Ofek Agmon Oct 10 '16 at 20:57
  • @Ofek For "isn't Amir Ziarati's answer good also", it's a fair answer; mine didn't address his because his didn't exist when I'd posted mine. I've edited my answer to address differences with `@Named`, but in short they should both work just fine. Custom qualifiers are a little extra code but can also provide a little more IDE help and type safety. – Jeff Bowman Oct 10 '16 at 22:56
10

I do not think it is a good idea to use the new operator within the constructor of the Module. This would create an instance of each of your provided objects upon initialization of your object graph (i.e. when you call new ApplicationModule()) instead of when Dagger needs the object for the first time. In this case (with only two objects), it would be negligible, but in larger projects this could cause a bottleneck upon the start of the application. Instead, I would follow the suggestion by @sector11, and instantiate your objects in the @Provides annotated methods.

As for providing two objects of the same type, both @Jeff and @Amir are correct. You can either use the provided @Named() qualifier, or create your own qualifiers, like so:

@Qualifier @Retention(RetentionPolicy.RUNTIME)
public @interface RectangleShape {}

@Qualifier @Retention(RetentionPolicy.RUNTIME)
public @interface CircleShape {}

Then your ApplicationModule should look like this:

@Module
public class ApplicationModule {

    @Provides @RectangleShape // @Named("rectangle")
    public Shape provideRectangle() {
        return new Rectangle();
    }

    @Provides @CircleShape // @Named("circle")
    public Shape provideCircle() {
        return new Circle();
    }

}

With this you can inject these objects into your classes like this:

@Inject @RectangleShape /* @Named("rectangle") */ public Shape mRectangle;
@Inject @CircleShape /* @Named("circle") */ public Shape mCircle;

If you need to provide the instances of your Shape classes without an @Inject annotation, you can do so in your Component class:

@Component(modules = { ApplicationModule.class })
public interface ApplicationComponent {

    void inject(MyApplication application);

    @RectangleShape // @Named("rectangle")
    Shape getRectangle();

    @CircleShape // @Named("circle")
    Shape getCircle();

}

These methods will provide the same instance of each class provided by the @Provides annotated methods.

Bryan
  • 14,756
  • 10
  • 70
  • 125
  • I understand what you saying about creating the classes in the @Porvides method and not in the constructor to prevent a bottleneck. but, for example, I also have a SharedPrefs class, which is created in the ApplicationModule, and I am using it in multiple classes using `applicationComponent.provdieSharedPrefs();` -> So, if I do the `new` in the @Provides method -> I will have a lot of instances from the same class. – Ofek Agmon Oct 10 '16 at 21:04
  • @OfekAgmon The `Component` you provide to Dagger is an `interface`, within which you can place getter methods to provide instances of your classes in that manner. Therefore if you create a method `getSharedPreferences()` in your `ApplicationComponent` that returns an instance of `SharedPreferences`, Dagger will always provide the same instance of `SharedPreferences` from your `ApplicationModule`. There is no need to create an instance of `SharedPreferences` outside of the `@Provides` annotated methods. – Bryan Oct 11 '16 at 12:29
4

In addition to @Named and custom qualifiers (shown in other responses), you can also use a custom qualifier with an enum parameter:

// Definition

@Qualifier
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface ShapeType {
  ShapeTypeEnum value(); /* default ShapeTypeEnum.RECTANGLE; */
}

public enum ShapeTypeEnum {
  RECTANGLE, CIRCLE
}

// Usage

@Provides @ShapeType(ShapeTypeEnum.RECTANGLE)
public Shape provideRectangle() {
    return new Rectangle();
}

@Inject @ShapeType(ShapeTypeEnum.RECTANGLE) Shape rectangle;

This is an hybrid between @Named (which requires String keys, which is error-prone and can't be auto-completed) and custom qualifiers (which requires a file for each implementation).

Albert Vila Calvo
  • 15,298
  • 6
  • 62
  • 73