📜 ⬆️ ⬇️

Dagger 2 and the structure of the application for Android


Good day! For more than a year our team has been developing the MyOffice client for the Android platform (we are developing the MyOffice application for all popular platforms).

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

Why Dagger 2


Before we started using Dagger 2, we didn’t use the Dependency Injection (DI) pattern. This is similar to how to add too much cereal to porridge: our code was too connected and this prevented free testing and editing the code.

In the same period, Google announced the library Dagger 2 - it was the new version. We compared the available analogues and for the mail client MyOffice stopped on it.
')
The Dagger 2 library has several advantages over other Dependency Injection libraries. Its principal advantage is work on the principle of code generation, without reflection. From this it follows that any errors related to the construction of a dependency graph will be found even at the time of compiling 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, 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 project Unit and UI tests, which in turn will lead to an increase in the stability of the application.

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

We will look at the main parts of Dagger 2:

and tell you how to use additional parts Dagger 2:

Types of @ Inject


There are several ways to query dependencies:
1) Implementation in the class constructor. The bonus of this option is the implicit availability of using this dependency for implementation (ManagerA is not necessary to be written 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) Introduction to the class field. Fields should not be private or final.

 @Inject ManagerB managerB; 


4) Calling the getter 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 marked with the @ Module annotation, and the methods generating dependencies 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 references them.

A module may contain a constructor with a parameter if it needs data from outside to resolve dependencies. The presence of the designer makes an important difference in the creation of the 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


The component is the link between the modules and dependency claimants. You can give a dependency through a component method (to which an object requesting dependencies will be passed) or via a getter (which returns the dependency). In one component there can be 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. Further we specify how dependencies will be resolved. You need to add a list of modules that will generate dependencies.

In the 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 that contains both a method and a getter will look like this:

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


Next you need to build a project. Classes of the Dagger type will be generated. Name of your Component that is inherited from your component. To create an instance of a component, use the builder. Depending on whether the module has a constructor with parameters or not, we can act differently.

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 modified:
 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 scoop application. The @ Scope annotation and its heirs mark methods in modules that generate objects for implementation. If the Produce method is marked with a scop, then any component using this module must be marked with the same scop.

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

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

Application level


Annotations defining scopes 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 specified in its includes, the same scop should be used, otherwise at compile time you will receive an error of constructing a dependency graph.

Now we need to 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 here that DI is convenient to use for tests, and we would like to be able to replace db with its imitation. For this, it is advisable to bring DbHelper into 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 resolve it on its own. ontext is taken from the module that references it:
 @Module(Includes={DbModule.class})  : <source lang="Java">comp = DaggerAppComponent.builder() .appModule(new AppModule(this)) .dbModule(new MockDbModule()) .build(); 


Activity Level


There can be several Activity Objects in the application, and their components must be associated with the Application component. Consider the parameters of annotations @ Component and @ Subcomponent and their participation in the construction of a dependency graph.

Suppose we have an EventBus manager for communication between the Activity and the snippet. 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 during compilation, we are immediately told that ActComponent cannot inject the DbHelper dependency. Magic, of course, did not happen. We have two different unbound dependency graphs. And the second graph does not know where to get DbHelper.

We have two options: either link the components through the interface, which will provide us with all the necessary dependencies, or, using the first component, create the second, then the graph will turn one.

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 dependencies to 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 the @ 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 a Subcomponent has a module with a parameterized constructor, it must be passed to our method. Otherwise, an error will occur when 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 an ActComponent or ActModule contains several other modules, you will need to increase the number of parameters of the Plus method in order to be able to transfer the modified module:

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


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

Fragment level


Introducing dependencies into 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 description 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 components for each fragment, even if we need to implement only one dependency. This will facilitate our work if it is necessary 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 the 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) } 


We now turn to the application. We need to pull the component out of the Activity regardless of the specific Activity. For this 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 a 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) Use the @ Subcomponent and plus method to create it:
 @Subcomponent(modules = {ManagerBModule.class}) public interface SecondFrComponent { void inject(SecondFragment fragment); interface PlusComponent { SecondFrComponent plusSecondFrComponent(ManagerBModule module); } } 


To avoid duplication of code, we put the most common dependencies and common code into 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> T getActComponent(Class<T> clazz){ Activity activity = getActivity(); HasComponent<T> has = (HasComponent<T>) activity; return has.getComponent(); } } 


Now the initialization of the component 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›


Suppose we have a manager that is initialized for a long time. It would not be desirable, that at an application launch all such dependences at once occupied the main flow. It is necessary to postpone the introduction of these dependencies until they are used. To do this, Dagger 2 has Lazy and Provider interfaces that implement deferred dependency initialization.
 @Inject Lazy<ManagerA> managerA; @Inject Provider<ManagerA> managerA; 


If ManagerA has a certain scop, their behavior is identical, but if there is no scop, Lazy caches the dependency after initialization, and the 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


Also, asynchronous dependency initialization is being developed. 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<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 turn in Rx Observable. Done!

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

GitHub example
Official documentation
Jake Wharton Presentation
Good presentation in Russian

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

Source: https://habr.com/ru/post/274025/


All Articles