This article discusses the use of the specialized Dagger 2 module under android and implies that you have basic knowledge of Dagger 2.
In Dagger 2.10, a new module was introduced specifically for Android. This module is supplied as an addition, consisting of an additional library and compiler.
In version 2.11 there were some small changes, in particular, some classes were renamed, so this version will be used.
Consider some of the features of Dagger 2 that will be used in the examples.
@Provides
methodsWe have the opportunity to write static @Provides
methods:
@Module public class RepositoryModule { @Provides public static NewsRepository newsRepository(SQLiteDatabase db) { return new NewsRepositoryImpl(db); } }
The main difference between the static @Provides
method and the usual one is that it is jerked by the component directly, and not through the module instance. Static @Provides
methods can be used both in the abstract and in the normal class of the module. Static methods can be scope and unscope.
@Binds
Dagger 2 allows us to provide dependencies without the presence of @Provides
methods. This is achieved by having @Inject
over the constructor of the class we need to create.
Consider an example:
public class NewsRepositoryImpl implements NewsRepository { private SQLiteDatabase database; @Inject public NewsRepositoryImpl(SQLiteDatabase database) { this.database = database; } //.. } ... public class MyActivty extends BaseActivity { @Inject NewsRepositoryImpl newsRepo; @Override protected void onCreate(Bundle savedInstanceState) { getAppComponent.inject(this); } }
With this approach, we can write a concrete class as a type, we cannot query the dependency on the NewsRepository interface. Dagger 2 will not be able to find the desired implementation for this interface.
In order to circumvent this restriction, we need @Binds
in order to bind (tie) the implementation to the interface.
Features @Binds
:
@Qualifier/@Named
method.Example:
@Module public abstract class RepositoryModule { @Binds @Singleton public abstract NewsRepository newsRepository(NewsRepositoryImpl repo); }
Now we can safely write the following:
public class MyActivty extends BaseActivity { @Inject NewsRepository newsRepo; ...
The modules presented in the form of abstract classes have the following features:
@Binds
@Provide
methods. Non-static provider methods are not supported .@Multibinds
When using @Binds + @Inject
over a constructor, we don’t have to write and implement the @Provides
methods completely.
If the module has methods for binding only ( @Binds
), then it makes sense to make this module as an interface:
@Module public interface AppModule { @Binds @Singleton NewsRepository newsRepository(NewsRepositoryImpl newsRepository); }
A typical android application using Dagger 2 looks like this:
public class SomeActivity { @Inject Api api; @Override protected void onCreate(Bundle savedInstanceState) { DaggerAppComponent .builder() .app(getApplication()) .build() .inject(this); } }
There may also be sub components for different scopes (for example, Activity scope, Fragment scope).
Hence the following problems:
This problem is solved by a new module for android.
//Dagger 2 compile 'com.google.dagger:dagger:2.11' annotationProcessor 'com.google.dagger:dagger-compiler:2.11' //Dagger-Android compile 'com.google.dagger:dagger-android:2.11' annotationProcessor 'com.google.dagger:dagger-android-processor:2.11' // support compile 'com.google.dagger:dagger-android-support:2.11'
You can not just connect the dependencies only related to android. They go as an addition.
As it is known, the @Scope
annotation and its heirs mark methods in modules, as well as components / subcomponents that provide the dependencies we need.@Scope
determines the lifetime of the objects being created ( @Scope
), thus representing effective memory management.
Consider an example of the structure of the application for scopam
@Singletone
- the application level (Application), the root scopes, living the longest. (Components: Context, Database, Repository). What we may need most often.@ActivityScope
- the standard of living over the life of the activity. May not live long because of the frequent transition from one screen to another. (Components: Router, Facade). It makes sense to clean up everything that is not used on a specific activation.@FragmentScope
- the level of life throughout the life of the fragment. He lives the least, the change of fragments can be organized inside one activit. The same does not make sense to store that which is no longer used on a specific fragment and must be cleaned up. (Constituent: Presenter)In this example, Facade is the equivalent of UseCase / Interactor. The application has a structure consisting of 3 Ospreys for demonstration of how this can be applied with the help of the new Dagger 2 module. Also here is an option using only with the @ContributesAndroidInjector
annotation.
We start implementation:
1. Define our main module.
@Module(includes = {AndroidSupportInjectionModule.class}) public interface AppModule { @Singleton @Binds Repository repository(RepositoryImpl repository); @ActivityScope @ContributesAndroidInjector(modules = {MainActivityModule.class}) MainActivity mainActivityInjector(); }
The following has been added to this module:
AndroidSupportInjectionModule
is a built-in dagger-android module that, according to the documentation, must be necessarily added to the root component to ensure the availability of all AndroidInjector
. It is required to inject the DispatchingAndroidInjector (see below).@ContributesAndroidInjector
- This annotation will generate an AndroidInjector
for the return type, so dagger will be able to inject dependencies into this activity. A subcomponent for the MainActivity
with the @ActvitiyScope @ActvitiyScope
will also be generated. AndroidInjection
is essentially a subcomponent for a specific activation. We can also indicate which modules will apply only to this particular activation. This AndroidInjector
will have all the dependencies of this module ( AppModule
) plus the dependencies that are listed in the ContributesAndroidInjector
annotation modules.@ContributesAndroidInjector
- applied over abstract methods or over interface methods.2. MainActivityModule
@Module public interface MainActivityModule { @ActivityScope @Binds Facade facade(FacadeImpl facade); @ActivityScope @Binds MainRouter router(MainRouterImpl mainRouter); }
When using AndroidInector
, we will have an AndroidInector
instance available to us, since This activation is part of the graph. This is because we call AndroidInjection.inject(this)
, thereby passing the activation instance (see below).
An example of getting an activation instance as a dependency.
public class MainRouterImpl extends BaseRouterImpl<MainActivity> implements MainRouter { @Inject public MainRouterImpl(MainActivity activity) { super(activity); } @Override public void showSomeScreen() { replaceFragment(R.id.content, new MyFragment()); } }
3. We write our root component, which will contain our AppModule, as well as the only injection in Application.
@Singleton @Component(modules = { AppModule.class }) public interface AppComponent { @Component.Builder interface Builder { @BindsInstance Builder context(Context context); AppComponent build(); } void inject(App app); }
4. It is necessary to implement the HasActivityInjector interface in Application and inject the AndroidInector dispatcher.
public class App extends Application implements HasActivityInjector { @Inject DispatchingAndroidInjector<Activity> dispatchingAndroidInjector; @Override public void onCreate() { super.onCreate(); DaggerAppComponent .builder() .context(this) .build() .inject(this); } @Override public AndroidInjector<Activity> activityInjector() { return dispatchingAndroidInjector; } }
DispatchingAndroidInjector
needed to search for AndroidInector
for a specific Activity
.
5. Now we can take advantage of all this.
public class MainActivity extends Activity { @Inject Repository repository; //Singleton scope @Inject Facade facade; //activity scope @Inject MainRouter router; //activity scope public void onCreate(Bundle savedInstanceState) { AndroidInjection.inject(this); // super.onCreate(savedInstanceState); } }
As we can see here there are no components and subcomponents, but at the same time we have the possibility of obtaining dependencies with different scopes. In addition, we have a router, for the creation of which is necessary (as a dependency) the activation instance itself.
The design of AndroidInjection.inject(this)
can be rendered to the base class and everything will work as well.
How it works? When you call AndroidInjection.inject(this)
, Dagger 2 gets access to the Application, which implements the HasActivityInjector
interface, where through the dispatcher it finds the desired AndroidInector
(sub-component of the activit
6. We turn to the implementation of @FragemntScope
.
We need to update our MainActivityModule
:
@Module public interface MainActivityModule { @ActivityScope @Binds Facade facade(FacadeImpl facade); @ActivityScope @Binds MainRouter router(MainRouterImpl mainRouter); @FragmentScope @ContributesAndroidInjector(modules = {MyFragmentModule.class}) MyFragment myFragment(); }
We added a similar AndroidInject
design for the fragment, as we did for activating.
Thus, AndroidInject (subcomponent) will be generated for us for a specific fragment with the @FragmentScope @FragmentScope
. We will have the @Singleton
, @ActivityScope
that are specified in this module and those that are specified as modules for this fragment.
7. Add the basic activation and implementation of the HasSupportFragmentInjector
interface.
abstract public class BaseActivity extends AppCompatActivity implements HasSupportFragmentInjector { @Inject DispatchingAndroidInjector<Fragment> fragmentInjector; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { AndroidInjection.inject(this); super.onCreate(savedInstanceState); } @Override public AndroidInjector<Fragment> supportFragmentInjector() { return fragmentInjector; } }
By analogy with Application
, we will be AndroidInjector
manager, which will provide us with the one we need for the AndroidInjector
fragment (subcomponent).
8. MyFragmentModule
@Module public interface MyFragmentModule { @Binds MyView myView(MyFragment myFragment); }
In the same way as with activit, we use AndroidInjection
( AndroidSupportInjection
, if we use the support library), we can access the instance instance, because it is part of the graph, we can transfer it as a dependency, and also send it to some kind of interface.
Example presenter:
public class MyPresenter { private MyView view; // , .. private Facade facade; //@ActivityScope private MainRouter router; //@ActivityScope @Inject public MyPresenter(MyView view, Facade facade, MainRouter router) { this.view = view; this.facade = facade; this.router = router; } //... }
9. Inject into fragment
public class MyFragment extends Fragment implements MyView { @Inject MyPresenter presenter; //@FragmentScope @Override public void onAttach(Context context) { AndroidSupportInjection.inject(this); super.onAttach(context); presenter.doSomething(); } @Override public void onResult(String result) { //Todo } }
The design of AndroidSupportInjection
can be rendered to the base class.
In my opinion, the new android-dagger module provides a more correct provision of dependencies for android. We can put injection methods into base classes, get a more convenient division by scopes, we do not need to forward subcomponents, and in our dependency graph, activation and fragment objects that we can use as external dependencies, for example, in presenter, are available.
Source: https://habr.com/ru/post/335940/
All Articles