Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dagger 2 issue overriding single provides annotated method from a module in a library which app uses

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

  1. The method was in a module in the app module
  2. The method was annotated with @Provides
  3. The method had the same return type as the one you want to override

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

like image 800
qazimusab Avatar asked Nov 23 '15 21:11

qazimusab


People also ask

What is the use of dagger 2 in Android?

Dagger 2 is a compile-time android dependency injection framework that uses Java Specification Request 330 and Annotations. Some of the basic annotations that are used in dagger 2 are: @Module This annotation is used over the class which is used to construct objects and provide the dependencies.

Which of these annotations is not used in dagger?

Dagger cannot instantiate or inject classes that do not have neither @Inject nor @Provides annotations.

How does dagger generate code?

Dagger automatically generates code that mimics the code you would otherwise have hand-written. Because the code is generated at compile time, it's traceable and more performant than other reflection-based solutions such as Guice. Note: Use Hilt for dependency injection on Android.


1 Answers

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:

  1. 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.

  2. 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.

like image 59
Ognyan Avatar answered Sep 25 '22 08:09

Ognyan