Dagger 2 and the structure of the application for Android


    Good afternoon! Our team has been developing the MyOffice email client for the Android platform for more than a year (we are developing MyOffice applications for all popular platforms).

    Today we want to talk about the technologies that we use in the development of our email client. Namely, about the mechanisms of Dependency Injection in the form of the Dagger 2 library. In the article we will describe the main parts of the library and tell how to use them in an Android project.

    Why Dagger 2


    Before we started using Dagger 2, we did not use the Dependency Injection (DI) pattern. This is like adding too much cereal to the mess: our code was too connected and this interfered with free testing and editing of the code.

    At the same time, Google announced the Dagger 2 library - it was the newest option. We compared the available analogs and for the mail client MyOffice we focused on it.

    The Dagger 2 library has several advantages over other Dependency Injection libraries. Its fundamental advantage is the work on the principle of code generation, without reflection. It follows that any errors associated with building a dependency graph will be discovered even at the time of compilation of the project.

    By implementing DI in our project, we were able to beautifully get rid of the strong connectivity between the various modules of our application. We were also able to remove most of the singletones, the use of which was unjustified. Already now we see how the efficiency of writing and editing code has improved. In the future, we will have the opportunity to simplify the task of covering the Unit and UI project with tests, which in turn will increase the stability of the application.

    In this article, we want to provide a complete overview of Dagger 2.

    We will cover the main parts of Dagger 2:
    • dependency request options;
    • modules providing objects for implementation;
    • components connecting requests with objects for implementation;

    and tell you how to use additional parts of Dagger 2:
    • deferred and asynchronous dependency initialization.

    Views @ Inject


    There are several ways to request a dependency:
    1) Embedding in a class constructor. A bonus of this option is the implicit availability of using this dependency for implementation (ManagerA is not required to be specified in the module). If the constructor has parameters, it is necessary that they are in the dependency graph and can be implemented.
    //тут можно поместить @Scope зависимости
    public class ManagerA{
      @Inject
      public ManagerA(Context context){ /* */}
    }


    2) Implementation through the method. The method will be executed after calling the constructor.

    @Inject
    public void register(SomeDepends depends){
      depends.register(this);
    }


    3) Implementation in the class fields. Fields must not be private or final.

    @Inject ManagerB managerB;


    4) The getter call of the object we need. This getter is also used to link multiple dependency graphs.
    managerA = component.getManagerA();


    @ Module


    A module is a factory of objects that resolves our dependencies. It should be tagged with @ Module annotation, and dependency generating methods should be @ Provides. And if it is necessary to mark the scope, then we mark the module with one of the @ Scope annotations.

    Annotation @ Module may contain other modules.
    @Module(includes={SomeModule1.class, SomeModule2.class})


    Thus, the dependencies contained in them will be available in the module that refers to them.

    A module may contain a constructor with a parameter if it needs external data to resolve dependencies. The presence of a constructor makes an important difference in the creation of a component, which will be discussed below .
    @Module
    class AppModule{
      Application app;
      AppModule(App app){
        this.app = app;
      }
      @PerApplication
      @Provides
      Context provideContext(){return app;}
    }


    Cascading dependencies may also occur:
    @Provides
    RestAdapter provideRestAdapter(){return new RestAdapter();}
    @Provides
    GitHubApi provideRetrofitAdapter(RestAdapter adapter){
      return adapter.create(GitHubApi.class);
    }


    @ Component


    A component is the link between modules and dependency seekers. You can give the dependency through the method of the component (to which the object requesting the dependencies will be passed) or through the getter (which will return the dependency). One component can have both methods and getters. The names of the methods or getters are not important.

    In both cases, we first create the interface and mark it with the annotation @ Component or @ Subcomponent. Next, we specify how dependencies will be resolved. You must add a list of modules that will generate dependencies.

    In case of implementation through the method, the list of necessary dependencies is taken from the class itself and its base classes:
    class App{
      @Inject
      ManagerA managerA;
    } 


    A component containing both the method and the getter will look like this:

    @Component(modules={AppModule.class})
    interface AppComponent{
      void injectInto(App holder);
      ManagerA getManagerA ();
    }


    Next you need to assemble the project. Classes of the form DaggerName of your Component will be generated that are the descendants of your component. To create an instance of the component, use the builder. Depending on whether the module has a constructor with parameters or not, we can act in different ways.

    If there is a parameterized module constructor, then you need to set all such modules yourself:

    AppModule module = new AppModule(this);
    DaggerAppComponent.builder().appModule(module).build();
    //сгенерированный код
    public SecondActComponent build() { 
     if (appModule == null) {
       throw new IllegalStateException("appModule must be set");
     }
     return new DaggerAppComponent (this);
    }


    If not, then in addition to the builder, the create () method will be generated and the build () method will be changed:
    DaggerAppComponent.create();
    //сгенерированный код
    public static AppComponent create() { 
     return builder().build();
    }
    //сгенерированный код
    public AppComponent build() { 
      if (appModule == null) {
        this.appModule = new appModule();
     }
     return new DaggerAppComponent (this);
    }
    class App{
      @Inject
      ManagerA managerA;
      AppComponent component
      @Override
     public void onCreate(){
        //… инициализация компонента
        component.inject(this);
        //или
        managerA= component.getmanagerA();
        super.onCreate()
      }
    }


    @ Scope


    Consider Android and the use of scopes. The @ Scope annotation and its descendants mark the methods in the modules that generate the objects to embed. If the Produce method is marked with a scope, then any component using this module must be marked with the same scope.

    Different managers have different scopes. For example, DataBaseHelper should be one for the entire application. For this, a singleton was usually used. In Dagger there is such scope @ Singletone, which marks the objects needed in one instance for the entire application. But we decided to use our @ PerApplication scopes for a complete analogy of names with activity and fragment scopes.

    The name of the scope does not matter - the level of nesting of the components and their scopes is important.

    Application level


    Annotations that define scope are declared as follows:
    @Scope
    @Retention(RUNTIME)
    @interface PerApplication;


    It is used like this:
    class AppModule{
      //...
      @Provides
      @PerApplication
      DbHelper provideDbHelper(Context context){
        return new DbHelper(context);
      }
      @Provides
      @PerApplication
      Context provideContext(){
        return app;
      }
    }


    Within the framework of one module and those that are registered with it in includes, the same scope must be used, otherwise during compilation you will get an error in building the dependency graph.

    Now we must mark the components using this module:
    @PerApplication
    @Component(modules={AppModule.class})
    interface AppComponent{
      void inject(App);
    }
    class App extends Application{
      @Inject
      DbHelper dbHelper;
      Appcomponent comp;
      @Override
      onCreate(){
        super.onCreate();
        comp = DaggerAppComponent.builder()
          .appModule(new AppModule(this))
          .build();
     }
    }


    It is worth paying attention to the fact that DI is convenient to use for tests, and we would like to be able to replace db with its imitation. To do this, it is advisable to put DbHelper in a separate module:
    @Module
    class DBModule{
      @PerApp
      @Provides
      DbHelper dbhelper(Context context){
        return new DbHelper(context);}
    }


    As you can see, this module does not contain context and is not able to independently resolve it. The context is taken from the module that refers to it:
    @Module(Includes={DbModule.class})
    И теперь:
    comp = DaggerAppComponent.builder()
      .appModule(new AppModule(this))
      .dbModule(new MockDbModule())
      .build();


    Activity Level


    There can be several Activity objects in an application, and their components need to be associated with the Application component. Consider the annotation parameters @ Component and @ Subcomponent and their participation in the construction of the dependency graph.

    Suppose we have an EventBus manager for communication between an Activity and a fragment. Its scope is one instance of the manager for the Activity and fragments that are in the Activity.
    @Module
    class ActModule{
     @PerActivity
     @Provides
     provide Bus(){return new Bus();}
    @Component()
    interface ActComponent{
     inject(MainActivity activity);
    }
    class MainActivity extends Activity{
      @Inject DbHelper dbHelper;
      @Inject Bus bus;
    }


    But at compile time, they immediately tell us that ActComponent cannot inject the DbHelper dependency. Magic, of course, did not happen. We got two different unrelated dependency graphs. And the second graph does not know where to get DbHelper.

    We have two options: either connect the components through an interface that will provide us with all the necessary dependencies, or, using the first component, create the second, then we get one graph.

    The @Component annotation has a dependencies parameter that points to a list of component interfaces that provides the necessary dependencies.
    @Component(modules={ActModule.class}, dependencies={AppComponent.class})


    In this case, add dependency getter to the AppComponent.
    @PerApplication
    @Component(modules={AppModule.class})
    interface appComponent{
      void inject(App);
      DbHelper dbHelper();
    }
    class MainActivity extends Activity{
      @Inject DbHelper dbHelper;
      @Inject Bus bus;
      ActComponent component;
      onCreate(){
        AppComp appcomp = ((App)getApp).getAppComponent();
        ActMod actModule = new ActModule(this);
        component= DaggerActComponent.build
          .actmodule(actModule)
          .appComponent(appComp)
          .build();
        component.inject(this);}
    }


    For the second method, you need to mark our internal component with @ Subcomponent annotation. In addition to the list of modules, it has no other parameters.
    @Subcomponent(modules={ActModule.class})
    interface ActComponent{
      inject(MainActivity activity);}


    And in AppComponent we add the method returning ActComponent. There is a general rule that if Subcomponent has a module with a parameterized constructor, it must be passed to our method. Otherwise, an error will occur at the time the component is created.

    @PerApp
    @Component(modules={AppModule.class})
    interface AppComponent{
      void inject(App);
      ActComponent plusActModule(ActModule module);
    }
    //в activity
    onCreate(){
      AppComp appcomp = ((App)getApp).getAppComponent();
      ActMod actModule = new ActModule(this);
      actCom = appcomponent.plusActModule(actModule);
      actCom.inject(this);
    }


    The disadvantage of the SubComponent option is that if ActComponent or ActModule contain several other modules, then you will need to increase the number of Plus method parameters in order to be able to transfer the changed module:

    ActComponent plusActModule(ActModule module, BusModule busModule/*и т.д.*/);


    Total: the option with the component and dependencies looks more flexible, but it will be necessary to describe all the necessary dependencies in the interface.

    Fragment Level


    Embedding dependencies in fragments is more interesting, since a fragment can be used in several Activities. For example, an application with a list of objects and their detailed descriptions, when two Activities are used on the phone, and one Activity with two fragments on the tablet.

    For our email client, we decided to use our own component for each fragment, even if you need to implement only one dependency. This will facilitate our work if you need to update the list of dependencies in the fragment. There are also two options for creating a component:

    Use @ Component and its dependencies parameter
    interface ActComponent{
      Bus bus();
      DbHelper dbhelper();
    }
    @Component(modules={ManagerAModule.class}, dependencies={FirstActComponent.class})
    interface FirstFragmentComponent{
      inject(FirstFragment fragment);
    }


    We immediately see the problem: our component depends on the specific component of Activity. A suitable solution is when an interface is created for each component of the fragment that describes the dependencies necessary for it:

    @Component(modules={ManagerAModule.class}, dependencies={FirstFrComponent.HasFirstFrDepends.class})
    interface FirstFragmentComponent{
      void inject(FirstFragment fragment);
      interface HasFirstFrDepends {
        Bus bus();
        DbHelper dbHelper();
      }
    }
    @PerActivity
    @Component(modules = {BusModule.class})
    interface FirstActComponent extends FirstFrComponent.HasFirstFrDepends {
      inject(FirstActivity activity)
    }


    Now let's move on to the application. We need to pull the component from the Activity, regardless of the specific Activity. For this we use:
    interface HasComponent{
      Component getComponent();
    }


    Total we inherit our Activities from it:
    class FirstActivity extends Activity implements HasComponent{
      FirstActComponent component;
      FirstActComponent getComponent(){
        return component;	
      }
    }


    And now we can use this interface instead of a specific Activity:
    class FirstFragmentextends Fragment{
      FirstFrComponent component;
      onActivityCreated(){ 	
        HasComponent has = (HasComponent) activity;
      FirstFrComponent.HasFirstFrDepends depends = has.getComponent();
      component = DaggerFirstFrComponent.builder()
        .hasFirstFrDepends(actComponent)
        .build();
      component.inject(this);
      }
    }


    2) Use @ Subcomponent and the plus method to create it:
    @Subcomponent(modules = {ManagerBModule.class})
    public interface SecondFrComponent {
      void inject(SecondFragment fragment);
      interface PlusComponent {
        SecondFrComponent plusSecondFrComponent(ManagerBModule module);
      }
    }


    To avoid code duplication, we take out the most common dependencies and the general code in the base Activity and fragment:
    public abstract class BaseActivity extends AppCompatActivity {
      @Inject
      protected Bus bus;
      @Override
      protected void onCreate(Bundle savedInstanceState) {
        initDiComponent();
        super.onCreate(savedInstanceState);
    }
    abstract protected void initDiComponent();
    protected AppComponent getAppComponent() {
      return ((App) getApplication()).getComponent();
      }
    }
    public abstract class BaseFragment extends Fragment {
      @Inject
      Bus bus;
      @Override
      public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        initDiComponent();
      }
      abstract protected void initDiComponent();
      public  T getActComponent(Class clazz){
        Activity activity = getActivity();
        HasComponent has = (HasComponent) activity;
        return has.getComponent();
      }
    }


    Now the component initialization in the fragment looks like this:
    @Override
    protected void initDiComponent() {
      FirstFrComponent.HasFirstFrDepends depends =     getActComponent(FirstFrComponent.HasFirstFrDepends.class);
      DaggerFirstFrComponent.builder()
        .hasFirstFrDepends(depends)
        .build()
        .inject(this);
    }


    Lazy ‹T› and Provider ‹T›


    Let's say we have a manager that takes a long time to initialize. I would not want all of these dependencies to occupy the main thread at once when starting the application. It is necessary to postpone the implementation of these dependencies until they are used. To do this, Dagger 2 has the Lazy and Provider interfaces that implement deferred dependency initialization.
    @Inject
    Lazy managerA;
    @Inject
    Provider managerA;


    If ManagerA has some kind of scope, then their behavior is identical, but if there is no scope, Lazy caches the dependency after initialization, and Provider generates a new one each time.
    class ManagerA{
      @Inject
      ManagerA(){
        Log.i("GTAG", "managerA init");
      }
    }
    Log.i("GTAG", "managerA hashcode: " + managerA.get().hashCode());
    Log.i("GTAG", "managerA hashcode: " + managerA.get().hashCode());
    Log.i("GTAG", "managerA hashcode: " + managerA.get().hashCode());
    Lazy-вариант:
    managerA init
    mAct managerA hashcode: 59563176
    mAct managerA hashcode: 59563176
    mAct managerA hashcode: 59563176
    Provider-вариант:
    managerA init
    managerA hashcode: 162499239
    managerA init
    managerA hashcode: 2562900
    managerA init
    managerA hashcode: 32664317


    Producer


    Development of asynchronous dependency initialization is also underway. In order to look at them, you need to add:
    compile 'com.google.dagger: dagger-producers: 2.0-beta'

    And a small example:
    @ProducerModule
    public class AsyncModule {
      @Produces
      ListenableFuture produceHugeManager() {
        return Futures.immediateFuture(new HugeManager());
      }
    }
    @ProductionComponent(modules = AsyncModule.class)
    public interface AsyncComponent {
       ListenableFuture hugeManager();
    }
    void initDiComponent() {
      AsyncComponent component = DaggerAsyncComponent
        .builder()
        .asyncModule(new AsyncModule())
        .executor(Executors.newSingleThreadExecutor())
        .build();
      ListenableFuture hugeManagerListenableFuture = component.hugeManager();
    }


    We receive ListenableFuture with which we can already work, for example, to wrap in Rx Observable. Done!

    Below are links to the project with examples and useful presentations:

    Example on GitHub
    Official documentation
    Presentation by Jake Wharton
    Good presentation in Russian

    In the following articles we are ready to talk about our mobile developments and the technologies used. Thank you for your attention and Happy New Year!

    Also popular now: