Gabriel Sanmartin Gabriel Sanmartin - 27 days ago 12
Android Question

Dagger2 and Android

I am trying to implement Dagger Dependency Injection into my app but I am having a hard time understanding how it works, especially coming from Spring where DI was much easier and much more declarative.

What I want to do is have a bunch of inject-ready objects that can be used throughout my app, that is the SharedPreferences, the Network objects (OkHttp, Retrofit, Picasso...), and EventBus and a SchedulerProvider object for RxJava.

This sample seems to offer everything I need but I am having trouble grasping some concepts.

In this other sample referenced in the previous page they create a GithubService that uses the Retrofit object provided in the NetModule. For that they create a GithubComponent like this:

@UserScope
@Component(dependencies = NetComponent.class, modules = GitHubModule.class)
public interface GitHubComponent {
void inject(MainActivity activity);
}


They are using a UserScope annotation that defines its own scope. Since @Singleton cannot be used, does this mean that the object will not be a Singleton? How do scopes really affect the DI? It seems they are only declaring a named-scope with no more effect, but I'm not sure.

Also, my app is built using Activities with Fragments. Do I have to create a Component for every Fragment in my app? i.e. I need to use my REST api services all throughout the app, do I have to declare a Component for every screen using them? This raises the amount of boilerplate code required and thus sounds not very clean.

Answer

Components ought to be the "big DI provider" that provides everything for a specific scope.

For example, you could have a SingletonComponent with @Singleton scope that has every single module added to it that has at least one @Singleton scoped provider method.

@Singleton
@Component(modules={NetworkingModule.class, DatabaseModule.class, MapperModule.class, UtilsModule.class})
public interface SingletonComponent {
    // provision methods
    OkHttpClient okHttpClient();
    RealmHolder realmHolder();
    // etc.
}

You can have the provision methods defined per module.

public interface DatabaseComponent {
    RealmHolder realmHolder();
}

In which case you'd have

@Singleton
@Component(modules={NetworkingModule.class, DatabaseModule.class, MapperModule.class, UtilsModule.class})
public interface SingletonComponent 
      extends NetworkingComponent, DatabaseComponent, MapperComponent, UtilsComponent {
    // provision methods inherited
}


In a Module, you can specify a factory method ("provider method") that specifies how to create a particular type of dependency.

For example,

@Module
public class NetworkingModule {
    @Provides
    @Singleton
    OkHttpClient okHttpClient() {
        return new OkHttpClient.Builder()./*...*/.build();
    }

    @Provides
    @Singleton
    Retrofit retrofit(OkHttpClient okHttpClient) {
        // ...
    }
}

You can imagine the @Singleton scope as the big DI container that Spring would have given you.


You can also provide instances of the class using @Inject annotated constructor. This can receive any class from the component that is able to instantiate it from provider methods within that scoped component's modules (and unscoped dependencies, of course).

@Singleton
public class MyMapper {
    @Inject
    public MyMapper(RealmHolder realmHolder, OkHttpClient okHttpClient) { // totally random constructor for demo
    }
}

or

@Singleton
public class MyMapper {
    @Inject
    RealmHolder realmHolder;

    @Inject
    OkHttpClient okHttpClient;

    @Inject
    public MyMapper() {
    }
}

Then this will be available in the component, you can even make provision method for it to make it inheritable in component dependencies:

@Singleton 
@Component(modules={...})
public interface SingletonComponent {
    MyMapper myMapper();
}


With Dagger2, you can also additionally create "subscoped components", that inherit all dependencies provided from a component of a given scope.

For example, you can inherit all @Singleton scoped components, but you can still have new scoped dependencies per that new scope, such as @ActivityScope.

@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface ActivityScope {
}

Then, you can create subscoped components using either subcomponents, or component dependencies.

  • Subcomponent:

.

@ActivityScope
@Subcomponent(modules={MainActivityModule.class})
public interface MainActivityComponent {
}

Then this can be created in its parent scoped component:

@Singleton 
@Component(modules={...})
public interface SingletonComponent {
    MainActivityComponent mainActivityComponent(MainActivityModule module);
}

Then you can use the singleton component to instantiate this:

SingletonComponent singletonComponent = DaggerSingletonComponent.create();
MainActivityComponent mainActivityComponent = singletonComponent.mainActivityComponent(new MainActivityModule(mainActivityHolder));
  • Component dependency:

.

@ActivityScope
@Component(dependencies={SingletonComponent.class}, modules={MainActivityModule.class})
public interface MainActivityComponent extends SingletonComponent {
}

For this to work, you must specify provision methods in the superscoped component.

Then you can instantiate this like so:

SingletonComponent singletonComponent = DaggerSingletonComponent.create();
MainActivityComponent mainActivityComponent = DaggerMainActivityComponent.builder()
                       .singletonComponent(singletonComponent)
                       .mainActivityModule(new MainActivityModule(mainActivityHolder))
                       .build();


In Dagger2, you can therefore obtain dependencies either via:

  • @Inject annotated constructor parameters
  • @Inject annotated fields on classes with @Inject annotated constructor
  • from @Component provision methods
  • via manual field injection method defined in component (for classes you can't create using @Inject annotated constructor)

Manual field injection can happen for classes like MainActivity, that you yourself don't create.

Manual field injection injects only the specific class that you are injecting. Base-classes don't get automatically injected, they need to call .inject(this) on component.

It works like this:

@ActivityScope
@Subcomponent(modules={MainActivityModule.class})
public interface MainActivityComponent {
    void inject(MainActivity mainActivity);
}

Then you can do:

public class MainActivity extends AppCompatActivity {
    @Override
    public void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        MainActivityComponent mainActivityComponent = DaggerMainActivityComponent.builder()
                          .singletonComponent(getSingletonComponent())
                          .mainActivityModule(new MainActivityModule(this))
                          .build(); // ensure activity `holder` instead, and retain component in retained fragment or `non-configuration instance`
        mainActivityComponent.inject(this);       
    }
}