В статье рассмотрим возможности использования Dagger 2 для реализации DI под ОС Android. Рассмотрим основные части, реализующие данный механизм в Dagger 2, на простом примере.
Подготовка
Первым делом создадим новый проект в Android Studio.
Теперь необходимо добавить в Gradle зависимости на Dagger 2. Для начала в build скрипте проекта добавим зависимость на android-apt. Эта библиотека необходима для корректной авто-генерации кода из аннотаций в Android Studio.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:1.1.0' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' } } allprojects { repositories { jcenter() } } |
Далее добавим зависимости на сам Dagger 2.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
apply plugin: 'com.android.application' apply plugin: 'com.neenbedankt.android-apt' android { compileSdkVersion 23 buildToolsVersion "21.1.2" defaultConfig { applicationId "javainside.com.daggertest" minSdkVersion 14 targetSdkVersion 23 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) testCompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:23.1.1' compile 'com.android.support:design:23.1.1' apt 'com.google.dagger:dagger-compiler:2.0.1' compile 'com.google.dagger:dagger:2.0.1' provided 'org.glassfish:javax.annotation:10.0-b28' } |
Как работает Dagger 2?
В основе работы Dagger 2 лежат аннотации стандарта JSR-330. В самой терминологии Dagger 2 можно выделить 3 ключевых слова:
- Зависимости
- Модули
- Компоненты
Которые выражаются в следующую формулу: “Компоненты предоставляют требуемые зависимости, которые определены в модулях.”
Зависимости
Dagger 2 поддерживает три способа внедрения зависимости:
- Field injection
- Constructor injection
- Method injection
Нас интересуют первые два варианта, но так как в Android экземпляры Activity или Service создаются самой средой, то мы не сможем использовать Constructor injection. Поэтому, для внедрения зависимостей в эти классы, необходимо добавить аннотацию @Inject для полей требующих зависимости, а далее в методе onCreate с использованием определенной структуры кода добавить зависимость. Об этом ниже.
Модули
Но сперва определим, где конфигурировать бины. В Dagger 2 для этих целей используются т.н. модули. Модуль инкапсулирует в себе знания о том как необходимо конструировать бины. Сам из себя модуль представляет класс с аннотацией @Module, в котором описываются методы с аннотацией @Provides. Именно в этих методах содержится основная логика конструирования бинов.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
@Module public class DaggerTestImageModelModule { @Provides public ImageModel provideImageModel(String url){ ImageModel model = new ImageModel(); model.setImageURL(url); return model; } @Provides public String provideUrl(){ return "http://goo.gl/stSwX0"; } } |
1 2 3 4 5 6 7 8 9 10 11 12 |
@Module public class DaggerTestSpeakerModule { @Provides @Singleton public Speaker provideSpeaker(){ Speaker speaker = new Speaker(); speaker.setName("Mikhailov Nikita"); speaker.setSpeech("Dagger 2 + Android"); return speaker; } } |
Модуль может содержать в себе описание другого модуля, для этого в параметр аннотации @Module нужно перечислить классы дополнительных модулей. Для тех кто работал со Spring можно провести анналогию модуля и класса конфигурации.
Компоненты
Компоненты представляют собой интерфейсы (или абстрактные классы), у которых могут быть два вида методов:
- методы возвращающие объект, но не имеющие параметров
- методы возвращающие void и могут содержать один параметр
Первые служат для получения бинов, вторые для установления зависимостей внутрь класса параметра.
1 2 3 4 5 6 7 8 9 10 11 |
@Singleton @Component( modules = { DaggerTestImageModelModule.class, DaggerTestSpeakerModule.class } ) public interface IDaggerTestComponent { void inject(MainActivity mainActivity); Speaker speaker(); } |
В этом коде в аннотации @Component мы указали какие модули использовать в качестве источника бинов.
Связка с Android
Теперь необходимо понять в каком месте проинициализировать наши компоненты в Android. Согласно специфике ОС основная единица приложения это класс Application, поэтому создадим собственную реализацию этого класса.
1 2 3 |
public class DaggerTestApp extends Application{ IDaggerTestComponent component; } |
Не забудем добавить в Manifest информацию о том, что у нас собственный класс Application.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:name=".DaggerTestApp" android:theme="@style/AppTheme"> <activity android:name=".activity.MainActivity" android:label="@string/app_name" android:theme="@style/AppTheme.NoActionBar"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> |
В методе onCreate соберем наш компонент.
1 2 3 4 5 6 7 8 9 |
@Override public void onCreate() { super.onCreate(); component = DaggerIDaggerTestComponent .builder() .daggerTestImageModelModule(new DaggerTestImageModelModule()) .daggerTestSpeakerModule(new DaggerTestSpeakerModule()) .build(); } |
Apt позволяет Dagger 2 сгенерировать собственную имплементацию нашего компонента. Нам достаточно лишь добавить в начале названия нашего интерфейса Dagger.
1 |
DaggerIDaggerTestComponent |
И у этой имплементации уже есть методы ожидающие наши модули:
1 2 |
.daggerTestImageModelModule(new DaggerTestImageModelModule()) .daggerTestSpeakerModule(new DaggerTestSpeakerModule()) |
Теперь остается лишь добавить доступ к компоненту из любой точки программы. Добавим следующий метод
1 2 3 |
public static IDaggerTestComponent component(Context context){ return ((DaggerTestApp) context.getApplicationContext()).component; } |
В классе MainActivity определим два поля ожидающих внедрения бинов.
1 2 3 |
Speaker speaker; @Inject ImageModel model; |
Чтобы внедрить зависимости в поля с аннотацией @Inject в метод onCreate добавим следующий код:
1 2 |
IDaggerTestComponent component = DaggerTestApp.component(this); component.inject(this); |
Видно, что используется компонент и объявленный в нем нами метод inject.
Воспользуемся второй возможностью нашего компонента и достанем из контейнера Dagger 2 бин Speaker.
1 |
speaker = component.speaker(); |
Модифицируем Layout и разместим наши информацию из бинов.
1 2 3 4 |
ImageView view = (ImageView) findViewById(R.id.image); model.downloadTask(view); TextView text = (TextView) findViewById(R.id.text); text.setText(speaker.getName() + "\n" + speaker.getSpeech()); |
Получим следующий результат.
Ссылка на GitHub
Спасибо за статью, стал немного въезжать в DI.
Рад стараться!
Спасибо за великолепную статью! Долго не мог понять что да как, наконец-то у Вас понял!)
Хотелось бы еще использование Dagger 2 и MVP архитектуры, будет ли такая статья?
Спасибо Никите за статью, даёт заинтересовать начинающего в DI)
А можно ли переделать сайт *** для просмотра на андроид устройствах?
Здравствуйте. Вы хотите адаптировать дизайн вашего магазина для мобильных устройств или сделать мобильное приложение для андроида?
А почему мы не инжектим Speaker, а только ImageModel
Что за класс ImageModel, откуда его взять? Хочу накодить у себя в студии, на второй строчке затуп.
В конце статьи есть ссылка на репозиторий с исходным кодом примера:
https://github.com/MihailovJava/DaggerTest
Конкретно класс ImageModel можно посмотреть тут:
https://github.com/MihailovJava/DaggerTest/blob/master/app/src/main/java/javainside/com/daggertest/model/ImageModel.java
Спасибо за статью. Но хотелось бы чтобы было проиллюстрировано как с помощью DI можно снизить связанность ПО. Например, чтобы проинжектить нового спикера достаточно добавить новый бин @Module public class DaggerTestSpeaker2Module и изменить список модулей в @Component(
modules = {
DaggerTestImageModelModule.class,
DaggerTestSpeaker2Module.class
или что?
Спасибо!