Developers Club geek daily blog

1 year, 1 month ago
Dagger 2 and structure of the application for Android
Good afternoon! Our command is engaged more than a year in development of an e-mail client Moyofis for the Android platform (applications Moyofis we is developed for all popular platforms).

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

Why Dagger 2


Before beginning to apply Dagger 2, we did not use a pattern of Dependency Injection (DI). It is similar to how to add too much grain to porridge: our code was too connected and it prevented free testing and editing a code.

During the same period of Google announced Dagger 2 library – it was the newest option. We compared the available analogs and for an e-mail client Moyofis stopped on her.

The Dagger 2 library has a number of advantages before other Dependency Injection libraries. Its basic advantage – work on the principle of a kodogeneration, without reflection. It follows from this that any errors connected with creation of a dependency graph will be found even at the time of compilation of the project.

Implementation of DI in our project we could get rid beautifully of strong coherence between different modules of our application. Also we could clean the majority of singlton which use was unjustified. Already now we see how efficiency of writing and editing a code increased. Further we will have an opportunity to simplify a problem of a covering of the Unit and UI project tests that in turn will lead to stability augmentation of operation of application.

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

We will consider the main parts of Dagger 2:
  • options of request of dependence;
  • the modules providing objects for implementation;
  • components, binding requests with objects for implementation;

also we will tell how to use additional parts of Dagger 2:
  • the postponed and asynchronous initialization of dependences.

Types @ Inject


There are several methods of request of dependence:
1) Implementation in the designer of a class. As a bonus of such option serves implicit availability of use of this dependence to implementation (not obligatory to prescribe ManagerA in the module). If the designer has parameters, it is necessary that they were in a dependency graph and could be implemented.
//тут можно поместить @Scope зависимости
public class ManagerA{
  @Inject
  public ManagerA(Context context){ /* */}
}


2) Implementation through a method. The method will be executed after the designer's challenge.

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


3) Implementation in class fields. Fields have to be not private and not final.

@Inject ManagerB managerB;


4) Challenge hetera of object necessary for us. This of heteras is also used for binding of several dependency graphs.
managerA = component.getManagerA();


@ Module


The module is the factory of objects permitting our dependences. It has to be marked with the summary @ Module, and methods generating dependences – @ Provides. And if it should be noted area of visibility, then the module is marked with one of summaries @ Scope.

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


Thus the dependences which are contained in them will be available in the module referring to them.

The module is might contain by the designer with parameter if for permission of dependences it needs data from the outside. Presence of the designer enters important difference to creation of a component what it will be told below about.
@Module
AppModule{
  Application app;
  AppModule(App app){
    this.app = app;
  }
  @PerApplication
  @Provides
  Context provideContext(){return app;}
}


Also cascade dependences can take place:
@Provides
RestAdapter provideRestAdapter(){return new RestAdapter()}
@Provides
GitHubApi provideRetrofitAdapter(RestAdapter adapter){
  return adapter.create(GitHubApi.class)
}


@ Component


The component is a link between modules and applicants of dependences. It is possible to give dependence through a component method (to which the object requesting dependences will be transferred) or through heteras (which will return dependence). In one component there can be both methods, and heteras. Names of methods or getter are not important.

In both cases we at first create the interface and we mark it with the summary @ Component or @ Subcomponent. Further we specify how dependences will be permitted. It is necessary to add the module list which will generate dependences.

In case of implementation through a method the list of necessary dependences undertakes from the class and its base classes:
class App{
  @Inject
  ManagerA managerA;
} 


The component containing both a method, and heteras will look so:

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


It is necessary to collect the project further. Classes of a type Daggernazvaniyevashegokomponenta, being successors of your component will be generated. For creation of a copy of a component we will use a bilder. Depending on whether the designer with parameters has the module or not, we can act differently.

If there is a parametrized designer of the module, then it is necessary to set all such modules with own hand:

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 is not present, then in addition to a bilder the create method will be generated () and the build method is 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
  onCreate(){
    //… инициализация компонента
    component.inject(this);
    //или
    managerA= component.getmanagerA();
    super.onCreate()
  }
}


@ Scope


Let's consider Android and application of skoup. The summary @ Scope and its successors marks methods in modules which generate objects for implementation. If the Produce-method is marked skoupy, then and any component using this module has to be marked with the same skoup.

Different managers have different areas of visibility. For example, DataBaseHelper has to be one for all application. For this purpose usually used Singleton. In Dagger is such skoup @ Singletone with which mark the objects necessary in one copy for all application. But we decided to use the skoup @ PerApplication for a perfect analogy of names to skoupa of an aktivita and a fragment.

The name of a skoup does not matter – the nesting level of components and their skoup is important.

Application level


The summaries defining areas of visibility appear so:
@Scope
@Retention(RUNTIME)
@interface PerApplication;


It is used so:
AppModule{
  //...
  @Provides
  @PerApplication
  dbHelper provideDbHelper(Context context){
  return new DbHelper(context)}

  @Provides
  @PerApplication
  context provideContext(){
    return app}
}


Within one module and those which are registered at it in includes it has to be used same skoup, otherwise in compilation time you receive an error of creation of a dependency graph.

Now we have to mark the components using this module:
@PerApplication
@Component(modules={AppModule.class}){
  void inject(App);
}

class App extends Application{
  @Inject
  DbHelper dbHelper;
  Appcomponent comp;

  @Override
  onCreate(){
    super.onCreate();
    comp = DaggerAppComponent.builder()
      .appModule(new AppModule(this))
      .build();
}


Here to be necessary to pay attention that it is convenient to DI to use for tests, and we would like to have an opportunity to change db for its simulation. For this purpose it is desirable to take out DbHelper in the separate module:
@Module
DBModule{
  @PerApp
  @Provides
  DbHelper dbhelper(Context context){
    return new DbHelper(context)}
}


As you can notice, this module does not contain context and is not capable to permit it independently. Context undertakes from the module referring to it:
@Module(Includes={DbModule.class})

И теперь:
<source lang="Java">comp = DaggerAppComponent.builder()
  .appModule(new AppModule(this))
  .dbModule(new MockDbModule())
  .build();


Activity level


Objects of Activity in the application can be several, and their components it is necessary to connect with the Application component. Let's consider parameters of summaries @ Component and @ Subcomponent and their participation in creation of a dependency graph.

Let's assume, we have a manager of EventBus for communication between Activity and a fragment. Its area of visibility is a one copy of the manager on Activity and fragments which are in Activity.
@Module
ActModule{
@PerActivity
@Provides
provide Bus(){return new Bus();}

@Component()
ActComponent{
inject(MainActivity activity);

class MainActivity extends Activity{
  @Inject DbHelper dbHelper;
  @Inject Bus bus;
}


But in compilation time say to us at once that ActComponent cannot implement dependence of DbHelper. Magic, of course, did not happen. At us two different untied dependency graphs turned out. And the second graph does not know from where to take DbHelper.

We have two options: or to connect components via the interface which will provide us all necessary dependences, or, using the first component, to create the second, then the graph will turn out one.

In the summary of @Component there is a dependencies parameter which indicates the list of interfaces of components providing necessary dependences.
@Component(modules={ActModule.class}, dependencies={AppComponent.class})


In this case we add to AppComponent of heteras of dependence.
@PerApplication
@Component(modules={AppModule.class}){
  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 it is necessary to mark our internal component with the summary @ Subcomponent. Except the module list, it has no other parameters.
@Subcomponent(modules={ActModule.class})
ActComponent{
  inject(MainActivity activity);}


And we add the method returning ActComponent to AppComponent. There is the general rule which is that if Subcomponent has the module with the parametrized designer, he needs to be transferred to our method. Otherwise at the time of creation of a component there will be an error.

@PerApp
@Component(modules={AppModule.class}){
  void inject(App);
  ActComponent plusActModule(ActModule module);
}

onCreate(){
  AppComp appcomp = ((App)getApp).getAppComponent();
  ActMod actModule = new ActModule(this);
  actCom = appcomponent.plusActModule(actModule);
  actCom.inject(this);
}


Lack of option with SubComponent is that if ActComponent or ActModule comprise several other modules, then it will be required to increase the number of parameters of the Plus method for a possibility of transfer of the changed module:

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


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

Fragment level


Implementation of dependences in fragments is more interesting as the fragment can be used in several Activity. For example, the application with the object list and their detailed description when on phone two Activity are used, and on the tablet – one Activity with two fragments.

For our e-mail client we decided to use the component under each fragment even if it is necessary to implement only one dependence. It will facilitate our work if it is required to update the list of dependences in a fragment. Here too there are two options of creation of a component:

We use @ Component and its dependencies parameter
ActComponent{
  Bus bus();
  DbHelper dbhelper();
}

@Component(modules={ManagerAModule.class}, dependencies={FirstActComponent.class})
FirstFragmentComponent{
  inject(FirstFragment fragment);
}


At once we see a problem: our component depends on the specific Activity component. The suitable solution – when for each component of a fragment is created the interface describing dependences, 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})
FirstActComponent extends FirstFrComponent.HasFirstFrDepends {
  inject(FirstActivity activity)
}


Now we will pass to application. We need to pull out a component from Activity regardless of specific Activity. For this purpose we use:
interface HasComponent<Component>{
  Component getComponent();
}


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


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


2) We use @ Subcomponent and the plus method for its creation:
@Subcomponent(modules = {ManagerBModule.class})
public interface SecondFrComponent {
  void inject(SecondFragment fragment);
  interface PlusComponent {
    SecondFrComponent plusSecondFrComponent(ManagerBModule module);
  }
}


To avoid duplication of a code, we take out the most general dependences and the general code in basic Activity and a 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> T getActComponent(Class<T> clazz){
    Activity activity = getActivity();
    HasComponent<T> has = (HasComponent<T>) activity;
    return has.getComponent();
  }
}


Now initialization of a component in a fragment looks so:
@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 who is initialized a progressive tense. It would not be desirable that at an application launch all such dependences at once occupied the main flow. It is necessary to postpone implementation of these dependences until their use. For this purpose in Dagger 2 there are Lazy and Provider interfaces implementing the postponed initialization of dependences.
@Inject
Lazy<ManagerA> managerA;

@Inject
Provider<ManagerA> managerA;


If ManagerA has certain skoup, then their behavior is identical, but if skoup is absent, Lazy after initialization caches dependence, and Provider generates every time new.
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


Developments of asynchronous initialization of dependences are also now conducted. To look at them, it is necessary to add:
compile 'com.google.dagger:dagger-producers:2.0-beta'

And small example:
@ProducerModule
public class AsyncModule {
  @Produces
  ListenableFuture<HugeManager> produceHugeManager() {
    return Futures.immediateFuture(new HugeManager());
  }
}

@ProductionComponent(modules = AsyncModule.class)
public interface AsyncComponent {
   ListenableFuture<HugeManager> hugeManager();
}

void initDiComponent() {
  AsyncComponent component = DaggerAsyncComponent
    .builder()
    .asyncModule(new AsyncModule())
    .executor(Executors.newSingleThreadExecutor())
    .build();
  ListenableFuture<HugeManager> hugeManagerListenableFuture = component.hugeManager();
}


We receive ListenableFuture with which we can already work, for example, to envelop in Rx Observable. It is ready!

Links to the project with examples and the useful presentations are included below:

Example on GitHub
Official documentation
The presentation from Jake Wharton
The good presentation in Russian

In the following articles we are ready to tell about our mobile developments and about the used technologies. Thanks for attention and with coming New year!

This article is a translation of the original post at habrahabr.ru/post/274025/
If you have any questions regarding the material covered in the article above, please, contact the original author of the post.
If you have any complaints about this article or you want this article to be deleted, please, drop an email here: sysmagazine.com@gmail.com.

We believe that the knowledge, which is available at the most popular Russian IT blog habrahabr.ru, should be accessed by everyone, even though it is poorly translated.
Shared knowledge makes the world better.
Best wishes.

comments powered by Disqus