Android dagger 2 implementation

Dagger & Android

One of the primary advantages of Dagger 2 over most other dependency injection frameworks is that its strictly generated implementation (no reflection) means that it can be used in Android applications. However, there are still some considerations to be made when using Dagger within Android applications.

Philosophy

While code written for Android is Java source, it is often quite different in terms of style. Typically, such differences exist to accomodate the unique performance considerations of a mobile platform.

But many of the patterns commonly applied to code intended for Android are contrary to those applied to other Java code. Even much of the advice in Effective Java is considered inappropriate for Android.

In order to achieve the goals of both idiomatic and portable code, Dagger relies on ProGuard to post-process the compiled bytecode. This allows Dagger to emit source that looks and feels natural on both the server and Android, while using the different toolchains to produce bytecode that executes efficiently in both environments. Moreover, Dagger has an explicit goal to ensure that the Java source that it generates is consistently compatible with ProGuard optimizations.

Of course, not all issues can be addressed in that manner, but it is the primary mechanism by which Android-specific compatibility will be provided.

Dagger assumes that users on Android will use R8 or ProGuard.

Why Dagger on Android is hard

One of the central difficulties of writing an Android application using Dagger is that many Android framework classes are instantiated by the OS itself, like Activity and Fragment , but Dagger works best if it can create all the injected objects. Instead, you have to perform members injection in a lifecycle method. This means many classes end up looking like:

This has a few problems:

Copy-pasting code makes it hard to refactor later on. As more and more developers copy-paste that block, fewer will know what it actually does.

More fundamentally, it requires the type requesting injection ( FrombulationActivity ) to know about its injector. Even if this is done through interfaces instead of concrete types, it breaks a core principle of dependency injection: a class shouldn’t know anything about how it is injected.

dagger.android

The classes in dagger.android offer one approach to simplify the above problems. This requires learning some extra APIs and concepts but gives you reduced boilerplate and injection in your Android classes at the right place in the lifecycle.

Another approach is to just use the normal Dagger APIs and follow guides such as the one here. This may be simpler to understand but comes with the downside of having to write extra boilerplate manually.

The Jetpack and Dagger teams are working together on a new initiative for Dagger on Android that hopes to be a large shift from the current status quo. While it is unfortunately not ready yet, this may be something to consider when choosing how to use Dagger in your Android projects today.

Читайте также:  Управление тв приставкой андроида со смартфона

Injecting Activity objects

Install AndroidInjectionModule in your application component to ensure that all bindings necessary for these base types are available.

Start off by writing a @Subcomponent that implements AndroidInjector , with a @Subcomponent.Factory that extends AndroidInjector.Factory :

After defining the subcomponent, add it to your component hierarchy by defining a module that binds the subcomponent factory and adding it to the component that injects your Application :

Pro-tip: If your subcomponent and its factory have no other methods or supertypes other than the ones mentioned in step #2, you can use @ContributesAndroidInjector to generate them for you. Instead of steps 2 and 3, add an abstract module method that returns your activity, annotate it with @ContributesAndroidInjector , and specify the modules you want to install into the subcomponent. If the subcomponent needs scopes, apply the scope annotations to the method as well.

Next, make your Application implement HasAndroidInjector and @Inject a DispatchingAndroidInjector to return from the androidInjector() method:

Finally, in your Activity.onCreate() method, call AndroidInjection.inject(this) before calling super.onCreate(); :

How did that work?

AndroidInjection.inject() gets a DispatchingAndroidInjector from the Application and passes your activity to inject(Activity) . The DispatchingAndroidInjector looks up the AndroidInjector.Factory for your activity’s class (which is YourActivitySubcomponent.Factory ), creates the AndroidInjector (which is YourActivitySubcomponent ), and passes your activity to inject(YourActivity) .

Injecting Fragment objects

Injecting a Fragment is just as simple as injecting an Activity . Define your subcomponent in the same way.

Instead of injecting in onCreate() as is done for Activity types, inject Fragment s to in onAttach() .

Unlike the modules defined for Activity s, you have a choice of where to install modules for Fragment s. You can make your Fragment component a subcomponent of another Fragment component, an Activity component, or the Application component — it all depends on which other bindings your Fragment requires. After deciding on the component location, make the corresponding type implement HasAndroidInjector (if it doesn’t already). For example, if your Fragment needs bindings from YourActivitySubcomponent , your code will look something like this:

Base Framework Types

Because DispatchingAndroidInjector looks up the appropriate AndroidInjector.Factory by the class at runtime, a base class can implement HasAndroidInjector as well as call AndroidInjection.inject() . All each subclass needs to do is bind a corresponding @Subcomponent . Dagger provides a few base types that do this, such as DaggerActivity and DaggerFragment , if you don’t have a complicated class hierarchy. Dagger also provides a DaggerApplication for the same purpose — all you need to do is to extend it and override the applicationInjector() method to return the component that should inject the Application .

The following types are also included:

Note: DaggerBroadcastReceiver should only be used when the BroadcastReceiver is registered in the AndroidManifest.xml . When the BroadcastReceiver is created in your own code, prefer constructor injection instead.

Support libraries

For users of the Android support library, parallel types exist in the dagger.android.support package.

TODO(ronshapiro): we should begin to split this up by androidx packages

Источник

Dagger 2.11 & Android

В данной статье рассматривается использование специализированного модуля Dagger 2 под android и подразумевается, что у вас есть наличие базовых знаний по Dagger 2.

В Dagger 2.10 был представлен новый модуль специально для Android. Данный модуль поставляется как дополнение, состоящий из дополнительной библиотеки и компилятора.
В версии 2.11 были некоторые небольшие изменения, в частности некоторые классы, были переименованы, поэтому будет использоваться именно эта версия.

Читайте также:  Лаунчеры для андроид как айфон

Базовая теория

Рассмотрим некоторые особенности Dagger 2, которые будут применяться в примерах.

static @Provides методы

У нас появилась возможность писать статические @Provides методы:

Основное отличие статического @Provides метода от обычного в том, что он дергается компонентом напрямую, а не через инстанс модуля. Статические @Provides методы можно использовать как в абстрактном, так и в обычном классе модуля. Статические методы могут быть scope и unscope.

@Binds

Dagger 2 позволяет нам предоставлять зависимости без наличия @Provides методов. Это достигается путем наличия @Inject над конструктором у класса, который нам необходимо создать.

При таком подходе мы можем писать в качестве типа конкретный класс, мы не можем запросить зависимость по интерфейсу NewsRepository. Dagger 2 не сможет найти нужную реализацию для данного интерфейса.

Для того чтобы обойти это ограничение, нам необходим @Binds , для того чтобы забайндить(привязать) реализацию к интерфейсу.

  • Применятся над абстрактным методом или над методом интерфейса модуля.
  • Возвращаемый тип метода — это интерфейс, на который мы байндим реализацию.
  • В качестве параметра метода указывается не зависимости, а тип конкретной реализации.
  • Можно применять к методу @Qualifier/@Named .
  • Можно указывать scope.

Теперь мы можем смело писать следующее:

Модули представленные в виде абстрактных классов имеют следующие особенности:

  • Могут содержать абстрактные методы с аннотацией @Binds
  • Могут содержать только static @Provide методы. Не статические провайд методы не поддерживаются.
  • Могут содержать абстрактные методы с аннотацией @Multibinds

При использовании @Binds + @Inject над конструктором у нас нет необходимости писать и реализовать полностью @Provides методы.

Если в модуле методы только для байндинга ( @Binds ), то имеет смысл сделать этот модуль в виде интерфейса:

Dagger-Android

Типичное android приложение использующая Dagger 2 выглядит примерно так:

Также могут быть получение саб компонентов для разных скоупов (например Activity scope, Fragment scope).

Отсюда вытекают такие проблемы:

  • Копипаст подобного блока. Даже если вынесли данный код в базовый класс, мы все равно вынуждены будем вызывать метод inject’a.
  • Компонент/Subcomponent должен иметь метод для каждого класса, где он должен инжектить.
  • Если у нас многоуровневая структура скоупов, то нам надо “пробрасывать” саб компоненты по уровням.

Эту проблему решает новый модуль для android.

Подключение зависимостей

Нельзя просто подключить зависимости только относящиеся к android. Они идут как дополнение.

Реализация

Как известно аннотацией @Scope и её наследниками помечаются методы в модулях, а также компоненты/сабкомпоенты, которые предоставляют необходимые нам зависимости.
@Scope определяет время жизни создаваемых(представляемых) объектов, тем самым представляют эффективное управление памятью.

Рассмотрим пример структуры приложения по скоупам:

  • @Singletone — уровень приложения (Application), корневой скоуп, живущий дольше всех. (Составляющие: Context, Database, Repository). То что нам может понадобится чаще всего.
  • @ActivityScope — уровень жизни на протяжении жизни активити. Могут жить не долго из за частого перехода с одного экрана на другой. (Составляющие: Router, Facade). Имеет смысл подчистить все, что не используется на конкретной активити.
  • @FragmentScope — уровень жизнь на протяжении жизни фрагмента. Живет меньше всех, смена фрагментов может быть организована внутри одной активити. Та же не имеет смысла хранить то, что уже не используется на конкретном фрагменте и необходимо подчистить. (Составляющие: Presenter)

В данном примере под Facade подразумевается аналог UseCase/Interactor. Приложение имеет структуру состоящую из 3х скопов для демонстрации как это можно применить с помощью нового модуля Dagger 2. Также здесь рассмотрен вариант с использованием исключительно с аннотацией @ContributesAndroidInjector .

Читайте также:  Что значит загрузка андроида

Приступаем к реализации:

1. Определим наш главный модуль.

В данный модуль было добавлено следующее:

  • AndroidSupportInjectionModule — это встроенный модуль dagger-android, который согласно документации, должен быть обязательно добавлен в root компонент, для обеспечения доступности всех AndroidInjector . Он необходим для того чтобы заинжектить DispatchingAndroidInjector(см. ниже).
  • @ContributesAndroidInjector — Данная аннотация сгенерирует AndroidInjector для возвращаемого типа, таким образом даггер сможет инжектить зависимости в данную активити. Так же будет сгенерирован сабкомпонент для MainActivity со скоупом @ActvitiyScope . AndroidInjection — это по сути сабкомпонент для конкретной активити. Также мы можем указать какие модули будут относится только к этому конкретному активити. Данный AndroidInjector будет иметь все зависимости этого модуля( AppModule ) плюс зависимости которые указаны в модулях аннотации ContributesAndroidInjector .
    @ContributesAndroidInjector — применяется над абстрактными методами или над методами интерфейса.

2. MainActivityModule

При использовании AndroidInector , нам будет доступен инстанс активити, т.к. данная активити является частью графа.Происходит это потому, что мы вызываем AndroidInjection.inject(this) , тем самым передаем инстанс активити (см. ниже).

Пример получение инстанса активити в качестве зависимости.

3. Напишем наш root компонент, который будет содержать наш AppModule, а также единственный инжект в Application.

4. Необходимо реализовать интерфейс HasActivityInjector в Application и заинжектить диспечер AndroidInector’ов.

DispatchingAndroidInjector нужен для поиска AndroidInector для конкретного Activity .

5. Теперь мы можем всем этим воспользоватся

Как мы видим здесь нет никаких компонентов и сабкомпонентов, но при этом у нас есть возможность получение зависимостей с разными скоупами. К тому же у нас есть роутер, для создания которого необходим(в качестве зависимости) сам инстанс активити.

Конструкцию AndroidInjection.inject(this) можно вынести в базовый класс и так же все будет работать.

Как это работает? При вызове AndroidInjection.inject(this) , Dagger 2 получается доступ к Application который реализует интерфейс HasActivityInjector , где через диспетчер находит нужный AndroidInector (сабкомпонент активити) по классу активити и затем производит инициализацию зависимостей с нужным скоупом.

6. Перейдем к реализации @FragemntScope .

Нам необходимо обновить наш MainActivityModule :

Мы добавили аналогичную конструкцию AndroidInject для фрагмента, как мы делали для активити.
Таким образом для нас будет сгенерирован AndroidInject (сабкомпонент) для конкретного фрагмента с скоупом @FragmentScope . Нам будут доступны зависимости @Singleton , @ActivityScope которые указаны в данном модуле и те что указаны в качестве модулей для данного фрагмента.

7. Добавим базовую активити и реализацию интерфейса HasSupportFragmentInjector .

По аналогии с Application , активити у нас будет выступать диспетчером AndroidInjector , который будет нам предоставлять нужный нам для фрагмента AndroidInjector (сабкомпонент).

8. MyFragmentModule

Точно так же, как с активити, мы используем AndroidInjection ( AndroidSupportInjection , если используем библиотеку поддержки), нам доступен инстанс фрагмента, т.к. он является частью графа, мы можем передавать ее в качестве зависимости, а также забайндить на какой нибудь интерфейс.

9. Инджектим в фрагмент

Конструкцию AndroidSupportInjection можно вынести в базовый класс.

Вывод

По моему мнению, новый модуль android-dagger предоставляет более правильное предоставление зависимостей для android. Мы можем вынести в базовые классы методы инъекции, получили более удобное разделение по скоупам, нам не надо пробрасывать сабкомпоненты и у нас стали доступны в графе зависимости объекты активити и фрагмента, которые мы можем использовать в качестве внешней зависимости, например в presenter’e.

Источник

Оцените статью