GitHub Project Link
I have made a project on GitHub which is a model of the dagger 2 architecture of my projects actual architecture. This question will be based off of the GitHub project.
I have provided many code snippets in this question, however, it may be easier to just compile the project yourself in Android Studio to understand the problem.
If you check out the code, it won't compile. Go into AppModule.java and comment out both provides methods and it should compile.
The main question is the last line on this post.
https://github.com/qazimusab/Dagger2LibraryProject
Architecture
I have a library which contains all the code needed to make the application. The point of this architecture is that each app I create in the project should be able to use the library and ,through dagger 2, be able to provide different implementations for any single class or activity it wants in it's own module. At this point I only have one application in this sample project which uses the library.
The Problem
With dagger one, I had the same architecture, and in the app specific module (as opposed to the library module), I was able to add a new provides annotated method to override any implementation which was being provided in the any of the library modules as long as
With Dagger 2, the architecture works when I either don't override any provides or if I do, when I override every single provides in that module and remove that module from the includes from the Application specific module.
For example, in my project, I have an app and a library.
The app has an AppModule; the library has a CatModule to provide a Cat and CatFood, a dog module to provide a Dog and DogFood, and a LibraryModule to provide the activities.
CatModule.java
package com.example.qaziahmed.library.application.modules;
import com.example.qaziahmed.library.classes.Cat; import
com.example.qaziahmed.library.classes.CatFood; import
com.example.qaziahmed.library.classes.contract.ICat; import
com.example.qaziahmed.library.classes.contract.ICatFood;
import javax.inject.Singleton;
import dagger.Module; import dagger.Provides;
/** * Created by qaziahmed on 11/23/15. */ @Module public class
CatModule {
@Provides
@Singleton
ICat provideCat() {
return new Cat();
}
@Provides
ICatFood provideCatFood(){
return new CatFood();
} }
DogModule.java
package com.example.qaziahmed.library.application.modules;
import com.example.qaziahmed.library.classes.Dog; import
com.example.qaziahmed.library.classes.DogFood; import
com.example.qaziahmed.library.classes.contract.IDog; import
com.example.qaziahmed.library.classes.contract.IDogFood;
import javax.inject.Singleton;
import dagger.Module; import dagger.Provides;
/** * Created by qaziahmed on 11/23/15. */ @Module public class
DogModule {
@Provides
@Singleton
IDog provideDog() {
return new Dog();
}
@Provides
IDogFood provideDogFood(){
return new DogFood();
}
}
So, in my application module, I want to provide a house cat implementation of ICat instead of a generic cat and an AllNaturalDogFood implementation of IDogFood instead of just regular DogFood, then in my AppModule I add two provides to override those
AppModule.java
package com.example.qaziahmed.dagger2libraryproject.application;
import
com.example.qaziahmed.dagger2libraryproject.classes.AllNaturalDogFood;
import com.example.qaziahmed.dagger2libraryproject.classes.HouseCat;
import com.example.qaziahmed.library.application.modules.CatModule;
import com.example.qaziahmed.library.application.modules.DogModule;
import
com.example.qaziahmed.library.application.modules.LibraryModule;
import com.example.qaziahmed.library.classes.contract.ICat; import
com.example.qaziahmed.library.classes.contract.IDogFood;
import javax.inject.Singleton;
import dagger.Module; import dagger.Provides;
/** * Created by ogre on 2015-07-12 */ @Module(includes = {
LibraryModule.class,
DogModule.class,
CatModule.class }) public class AppModule {
@Provides
@Singleton
ICat provideHouseCat() {
return new HouseCat();
}
@Provides
IDogFood provideAllNaturalDogFood(){
return new AllNaturalDogFood();
} }
Now, when I run this setup, this is the error I get:
Error:com.example.qaziahmed.library.classes.contract.ICat is bound multiple times: @Provides @Singleton com.example.qaziahmed.library.classes.contract.ICat com.example.qaziahmed.dagger2libraryproject.application.AppModule.provideHouseCat() @Provides @Singleton com.example.qaziahmed.library.classes.contract.ICat com.example.qaziahmed.library.application.modules.CatModule.provideCat() Error:com.example.qaziahmed.library.classes.contract.IDogFood is bound multiple times: @Provides com.example.qaziahmed.library.classes.contract.IDogFood com.example.qaziahmed.dagger2libraryproject.application.AppModule.provideAllNaturalDogFood() @Provides com.example.qaziahmed.library.classes.contract.IDogFood com.example.qaziahmed.library.application.modules.DogModule.provideDogFood()
Now, if in AppModule.java, I also add provides annotated methods to provide Cat Food and Provide Dog and then remove CatModule.class and DogModule.class from the includes in App Module then it works.
However, the whole question is how do I override a single provides method in some module in the library without having to override every provides annotated method inside that specific module and then removing that module from the includes in AppModule.java
Will try to decrypt this quote from the Dagger 2 docs:
Dagger 2 doesn't support overrides. Modules that override for simple testing fakes can create a subclass of the module to emulate that behavior. Modules that use overrides and rely on dependency injection should be decomposed so that the overridden modules are instead represented as a choice between two modules.
In your current example you don't rely on dependency injection because your provides*
methods create simple new objects so you will be able to just create a subclass of the module, override the provides
method that you need overridden and then include that new module in your component.
When you have reliance on DI (and in reality you will at some stage of your project) like this:
@Provides
@Singleton
ICat provideCat(IBowtie bowtie) { // 'bowtie' needs to be injected
return new CatWithBowtie(Bowtie);
}
it comes to "Modules that use overrides and rely on dependency injection should be decomposed" which basically means: you have to split CatModule
in two: CatModule
with just providesCat
and 'CatFoodModule' with provideCatFood()
. Then your app's component you just use your new CatWithBowtieModule
instead of CatModule
.
There two useful advises:
In library projects split modules so there is just one provides*
method per module. Yes, it sounds like BS but this is the only way to provide easy overriding later in your app.
For a moment lets pretend that the library is given to you from a third party as a JAR/AAP and you don't even have the source. In that case you will not be able to reuse modules defined in the lib, so you will have to create all of them by yourself. This is exactly what happens with Dagger 2.
When you try to use modules from your lib in your app directly (as you did) these two projects are not two separate projects any more but one project that just looks like two projects (which are clusterf*ck tightly coupled). It is OK for the app to depend on the lib but it is not OK for the lib to depend on the app. It boils down to: In Dagger 2 it is best not to use cross (project) border modules
and components
.
Someone may ask: "What is the good of using Dagger 2 in a lib if I can't use lib's modules
/components
in my app?!". Well, you still will be able to use your dagger modules
/components
in your unit tests which is the main benefit of using Dagger after all. Also if your lib is meant to be used by other people you may (must?) provide a referent app which shows how to "wire" things so lib's users will be able to just copy that code if it suits them or at least see how to start.