Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How @Inject annotation would know which concrete class to instantiate under same interface?

I am using Dagger2.0 in Android app.

I am confused with @Inject annotation. I have two concrete class implementing the same interface. I am injecting one of the concrete class using @Inject annotation. Here, how @Inject annotation decides which concrete class to instantiate.

Example:

I have one interface.

Product.java

public interface Product {}

There are total two concrete classes ProductOne and ProductTwo.

ProductOne.class

public class ProductOne implements Product{

@Inject
public ProductOne() {}

}

Packaging class is the client.

Packaging.java

public class Packaging{

@Inject
public Packaging(Product product){}

}

Till this moment my package class uses instance of ProductOne class.

Confusion:

If I have one another concrete class ProductTwo with @Inject annotation.

public class ProductTwo implements Product {

@Inject
public ProductTwo() {}

}

Now in my Packaging class I want to use instance of ProductTwo class, So this @Inject annotation will work at this moment?

like image 461
Vijay Vankhede Avatar asked Oct 07 '15 12:10

Vijay Vankhede


2 Answers

This example will not work. We have to use @Named annotation for this case.

For above example in our Dagger Packaging module we have to provide ProductOne and ProductTwo dependency.

@Provides @Named("product one") Product provideProductOne() {
    return new ProductOne();
}


@Provides @Named("product two") Product provideProductTwo() {
    return new ProductTwo();
} 

Now when we need to inject this dependency we can inject it on following way.

public class Packaging{

Product product;

@Inject
public Packaging(@Named("product one") Product product){
    this.product = product;
}

}

If we need instance of ProductTwo then.

public class Packaging{

Product product;
@Inject
public Packaging(@Named("product two")Product product){
    this.product = product;
}

}

This @Named annotation is nothing but use of @Qualifier annotation included in javax.inject

@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Named {
  String value() default "";
}

We don't have to provide this declaration hence Dagger do this for us.

like image 117
Vijay Vankhede Avatar answered Oct 23 '22 07:10

Vijay Vankhede


I'm assuming that as you made no mention of either modules or components that you aren't overly familiar with them and how they work together.

Your example won't work as Dagger 2 doesn't know that in order to produce a Product it needs to use one of the ProductOne or ProductTwo classes. Even though Dagger 2 will process them (because they have both been marked with @Inject) it will not automatically assume that just because they implement Product that they should be used there. The reason for that is that it wouldn't know which one to use when there was more than one as in this case.

So, you have to create a binding from either ProductOne or ProductTwo with the interface Product. You do this through a module.

@Module
public class ProductOneModule {
  @Provides Product provideProduct(ProductOne productOne) {
    return productOne;
  }
}

A module just provides a set of reusable bindings. They are not actually used (or validated) unless they are used by a component. A component is the thing that encapsulates all this information and manages the creation of them, using modules and its bindings and factories created for classess with @Inject constructors.

If you create a component like this then dagger 2 will fail because as mentioned above it doesn't know how to produce a Product.

@Component
public interface PackagerOneComponent {
  Packager packager();
}

The error will be something like this:

Product cannot be provided without an @Provides-annotated method.
    Packager.(Product product)
    [parameter: Product product]

That means that when trying to create a Packager object it could not find a suitable binding for its Product parameter. The way to address this is to specify the module with its binding from Product < ProductOne.

@Component(modules = ProductOneModule.class)
public interface PackagerOneComponent {
  Packager packager();
}

Now it knows that to create a Product it needs to call ProductOneModule.provideProduct(ProductOne) and in order to call that it needs to create a ProductOne which it knows how to do because you've marked one of its constructor with @Inject.

Of course if you want to use ProductTwo then you can just create another module and component.

@Module
public class ProductTwoModule {
  @Provides Product provideProduct(ProductTwo productTwo) {
    return productTwo;
  }
}

@Component(modules = ProductTwoModule.class)
public interface PackagerTwoComponent {
  Packager packager();
}

The problem with using a qualifier in this case, either a custom one or Named is that with qualifiers you tightly couple the injection point with a specific implementation. That is definitely required in some cases, e.g. if you have two Long instances, one of which is a time out and one is a port you wouldn't want them to be confused and so you'd definitely need to use qualifiers to differentiate them.

However, in this case it's likely that some users or Packaging would want to use ProductOne and some would want to use ProductTwo. Otherwise, Packager should just take ProductOne or ProductTwo directly and avoid the interface.

This approach allows two different parts of your code to use Packager with two different implementations of Product, e.g. your production and your tests.

Of course, you can use two different implementations even if it is annotated with a qualifier but you'd still have to use a variety of this technique.

like image 24
Paul Duffin Avatar answered Oct 23 '22 06:10

Paul Duffin