Як налаштувати інжекцію залежності DAGGER з нуля в Android-проект?


100

Як користуватися Dagger? Як налаштувати Dagger на роботу в моєму Android-проекті?

Я хотів би використовувати Dagger у своєму проекті Android, але я вважаю це заплутаним.

EDIT: Dagger2 також виходить з 2015 04 15, і це ще більше заплутано!

[Це питання є "заглушкою", на яку я додаю свою відповідь, коли я дізнався більше про Dagger1 та дізнався більше про Dagger2. Це питання є скоріше керівництвом, а не "питанням".]



Дякуємо, що поділилися цим. Чи знаєте ви, як вводити класи ViewModel? Мій клас ViewModel не має жодного @AssistedInject, але він має залежності, які можуть бути надані графіком Dagger?
AndroidDev


Ще одне запитання, з Dagger2, чи можна мати об'єкт, і його посилання поділяється ViewModelі PageKeyedDataSource? Як і я використовую RxJava2, і хочу, щоб CompositeDisposable було поділено обома класами, і якщо користувач натискає кнопку назад, я хочу очистити одноразовий об'єкт. Я haved доданий випадок тут: stackoverflow.com/questions/62595956 / ...
AndroidDev

Вам краще помістити compositeDisposable всередину ViewModelі, можливо, передати той самий compositeDisposable як аргумент конструктора вашого користувацького PageKeyedDataSource, але я б не використовував Dagger для цієї частини, тому що тоді вам потрібні субскопізовані підкомпоненти, і Hilt насправді не підтримує це легко для вас.
EpicPandaForce

Відповіді:


193

Посібник для Dagger 2.x (переглянутий випуск 6) :

Кроки такі:

1.) додати Daggerдо своїх build.gradleфайлів:

  • Верхній рівень build.gradle :

.

// 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:2.2.0'
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' //added apt for source code generation
    }
}

allprojects {
    repositories {
        jcenter()
    }
}
  • build.gradle рівня програми :

.

apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt' //needed for source code generation

android {
    compileSdkVersion 24
    buildToolsVersion "24.0.2"

    defaultConfig {
        applicationId "your.app.id"
        minSdkVersion 14
        targetSdkVersion 24
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        debug {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    apt 'com.google.dagger:dagger-compiler:2.7' //needed for source code generation
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:24.2.1'
    compile 'com.google.dagger:dagger:2.7' //dagger itself
    provided 'org.glassfish:javax.annotation:10.0-b28' //needed to resolve compilation errors, thanks to tutplus.org for finding the dependency
}

2.) Створіть свій AppContextModuleклас, який забезпечує залежності.

@Module //a module could also include other modules
public class AppContextModule {
    private final CustomApplication application;

    public AppContextModule(CustomApplication application) {
        this.application = application;
    }

    @Provides
    public CustomApplication application() {
        return this.application;
    }

    @Provides 
    public Context applicationContext() {
        return this.application;
    }

    @Provides
    public LocationManager locationService(Context context) {
        return (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
    }
}

3.) створити AppContextComponentклас, який надає інтерфейс, щоб отримати класи, які можна вводити.

public interface AppContextComponent {
    CustomApplication application(); //provision method
    Context applicationContext(); //provision method
    LocationManager locationManager(); //provision method
}

3.1.) Ось як би ви створили модуль із реалізацією:

@Module //this is to show that you can include modules to one another
public class AnotherModule {
    @Provides
    @Singleton
    public AnotherClass anotherClass() {
        return new AnotherClassImpl();
    }
}

@Module(includes=AnotherModule.class) //this is to show that you can include modules to one another
public class OtherModule {
    @Provides
    @Singleton
    public OtherClass otherClass(AnotherClass anotherClass) {
        return new OtherClassImpl(anotherClass);
    }
}

public interface AnotherComponent {
    AnotherClass anotherClass();
}

public interface OtherComponent extends AnotherComponent {
    OtherClass otherClass();
}

@Component(modules={OtherModule.class})
@Singleton
public interface ApplicationComponent extends OtherComponent {
    void inject(MainActivity mainActivity);
}

Остерігайтеся: Вам потрібно надати @Scopeанотацію (наприклад, @Singletonабо @ActivityScope) на @Providesанотованому методі модуля, щоб отримати провайдера в межах вашого генерованого компонента, інакше він буде недописаний, і ви отримаєте новий екземпляр щоразу, коли будете вводити.

3.2.) Створіть компонент, що охоплює додаток, який визначає, що ви можете вводити (це те саме, що injects={MainActivity.class}в Dagger 1.x):

@Singleton
@Component(module={AppContextModule.class}) //this is where you would add additional modules, and a dependency if you want to subscope
public interface ApplicationComponent extends AppContextComponent { //extend to have the provision methods
    void inject(MainActivity mainActivity);
}

3.3.) Для залежностей, які ви можете створити за допомогою конструктора самостійно і не хочете перевизначати за допомогою @Module(наприклад, ви використовуєте аромати побудови замість того, щоб змінити тип реалізації), ви можете використовувати @Injectконстатований конструктор.

public class Something {
    OtherThing otherThing;

    @Inject
    public Something(OtherThing otherThing) {
        this.otherThing = otherThing;
    }
}

Крім того, якщо ви використовуєте @Injectконструктор, ви можете використовувати введення поля, не вимагаючи явного виклику component.inject(this):

public class Something {
    @Inject
    OtherThing otherThing;

    @Inject
    public Something() {
    }
}

Ці @Injectкласи конструкторів автоматично додаються до компонента того ж діапазону без необхідності чітко їх вказувати в модулі.

@SingletonОбласті видимості @Injectконструктора клас буде розглядатися в @Singletonконтекстних компонентах.

@Singleton // scoping
public class Something {
    OtherThing otherThing;

    @Inject
    public Something(OtherThing otherThing) {
        this.otherThing = otherThing;
    }
}

3.4.) Після того, як ви визначили конкретну реалізацію для даного інтерфейсу, наприклад:

public interface Something {
    void doSomething();
}

@Singleton
public class SomethingImpl {
    @Inject
    AnotherThing anotherThing;

    @Inject
    public SomethingImpl() {
    }
}

Вам потрібно буде "прив'язати" конкретну реалізацію до інтерфейсу з a @Module.

@Module
public class SomethingModule {
    @Provides
    Something something(SomethingImpl something) {
        return something;
    }
}

Короткий огляд для цього, оскільки Dagger 2.4 є наступним:

@Module
public abstract class SomethingModule {
    @Binds
    abstract Something something(SomethingImpl something);
}

4.) створити Injectorклас для обробки компонента на рівні програми (він замінює монолітний ObjectGraph)

(Примітка: Rebuild Projectстворити DaggerApplicationComponentклас будівельника за допомогою APT)

public enum Injector {
    INSTANCE;

    ApplicationComponent applicationComponent;

    private Injector(){
    }

    static void initialize(CustomApplication customApplication) {
        ApplicationComponent applicationComponent = DaggerApplicationComponent.builder()
           .appContextModule(new AppContextModule(customApplication))
           .build();
        INSTANCE.applicationComponent = applicationComponent;
    }

    public static ApplicationComponent get() {
        return INSTANCE.applicationComponent;
    }
}

5.) створити свій CustomApplicationклас

public class CustomApplication
        extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        Injector.initialize(this);
    }
}

6.) додати CustomApplicationдо свого AndroidManifest.xml.

<application
    android:name=".CustomApplication"
    ...

7.) Введіть свої заняття вMainActivity

public class MainActivity
        extends AppCompatActivity {
    @Inject
    CustomApplication customApplication;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Injector.get().inject(this);
        //customApplication is injected from component
    }
}

8.) Насолоджуйтесь!

+1.) Ви можете вказати Scopeдля своїх компонентів, за допомогою яких можна створити компоненти на рівні активності . Підскопи дозволяють забезпечити залежності, які вам потрібні лише для певного субскопа, а не для всієї програми. Зазвичай кожна активність отримує власний модуль із цим налаштуванням. Зауважте, що провайдер, що охоплює область, існує на компонент , тобто для того, щоб зберегти екземпляр для цієї діяльності, сам компонент повинен пережити зміну конфігурації. Наприклад, це може вижити через onRetainCustomNonConfigurationInstance()або мінометний розмах.

Для отримання додаткової інформації про субскопіювання ознайомтеся з посібником Google . Також дивіться цей сайт про методи надання, а також розділ залежностей від компонентів ) та тут .

Щоб створити власну область, потрібно вказати примітку про класифікатор області:

@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface YourCustomScope {
}

Щоб створити підскоп, потрібно вказати область для вашого компонента та вказати ApplicationComponentяк його залежність. Очевидно, вам потрібно вказати субскоп і на методах постачальника модулів.

@YourCustomScope
@Component(dependencies = {ApplicationComponent.class}, modules = {CustomScopeModule.class})
public interface YourCustomScopedComponent
        extends ApplicationComponent {
    CustomScopeClass customScopeClass();

    void inject(YourScopedClass scopedClass);
}

І

@Module
public class CustomScopeModule {
    @Provides
    @YourCustomScope
    public CustomScopeClass customScopeClass() {
        return new CustomScopeClassImpl();
    }
}

Зверніть увагу, що лише один компонент, що охоплює область, може бути вказаний як залежність. Подумайте про це саме так, як множинне успадкування не підтримується на Java.

+2.) Про @Subcomponent: по суті, масштаб @Subcomponentможе замінити залежність компонента; але замість того, щоб використовувати будівельник, наданий процесором анотацій, вам потрібно використовувати заводський метод компонентів.

Отже це:

@Singleton
@Component
public interface ApplicationComponent {
}

@YourCustomScope
@Component(dependencies = {ApplicationComponent.class}, modules = {CustomScopeModule.class})
public interface YourCustomScopedComponent
        extends ApplicationComponent {
    CustomScopeClass customScopeClass();

    void inject(YourScopedClass scopedClass);
}

Стає таким:

@Singleton
@Component
public interface ApplicationComponent {
    YourCustomScopedComponent newYourCustomScopedComponent(CustomScopeModule customScopeModule);
}

@Subcomponent(modules={CustomScopeModule.class})
@YourCustomScope
public interface YourCustomScopedComponent {
    CustomScopeClass customScopeClass();
}

І це:

DaggerYourCustomScopedComponent.builder()
      .applicationComponent(Injector.get())
      .customScopeModule(new CustomScopeModule())
      .build();

Стає таким:

Injector.INSTANCE.newYourCustomScopedComponent(new CustomScopeModule());

+3.): Будь ласка, ознайомтеся з іншими питаннями переповнення стека щодо Dagger2, і вони містять багато інформації. Наприклад, моя відповідна структура Dagger2 вказана у цій відповіді .

Дякую

Дякуємо за путівники Github , TutsPlus , Joe Steele , Froger MCS та Google .

Також для цього покрокового посібника з міграції я знайшов після написання цієї публікації.

А для пояснення обсягу Кирилом.

Ще більше інформації в офіційній документації .


Я вважаю, нам не вистачає реалізації програми DaggerApplicationComponent
Thanasis Kapelonis

1
@ThanasisKapelonis DaggerApplicationComponentавтоматично створюється APT у збірці, але я додам його.
EpicPandaForce

1
Мені просто довелося оприлюднити метод Injector.initializeApplicationComponent, оскільки моє CustomApplication знаходилось поза межами пакету, і все працює ідеально! Дякую!
Хуан Саравія

2
Трохи пізно, але, можливо, наступні приклади допоможуть комусь: github.com/dawidgdanski/android-compass-api github.com/dawidgdanski/Bakery
dawid gdanski

1
Якщо ви отримали "Попередження: Використання несумісних плагінів для обробки анотацій: android-apt. Це може призвести до несподіваної поведінки. " На кроці 1 змініть apt 'com.google.dagger: dagger-компілятор: 2.7' на annotationProcessor 'com.google.dagger: dagger-компілятор: 2.7' та видаліть усі apt конфігурації. Деталі можна знайти тут bitbucket.org/hvisser/android-apt/wiki/Migration
thanhbinh84

11

Посібник для Dagger 1.x :

Кроки такі:

1.) додати Daggerдо build.gradleфайлу залежність

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    ...
    compile 'com.squareup.dagger:dagger:1.2.2'
    provided 'com.squareup.dagger:dagger-compiler:1.2.2'

Також додайте, packaging-optionщоб запобігти помилці про duplicate APKs.

android {
    ...
    packagingOptions {
        // Exclude file to avoid
        // Error: Duplicate files during packaging of APK
        exclude 'META-INF/services/javax.annotation.processing.Processor'
    }
}

2.) створити Injectorклас для обробки ObjectGraph.

public enum Injector
{
    INSTANCE;

    private ObjectGraph objectGraph = null;

    public void init(final Object rootModule)
    {

        if(objectGraph == null)
        {
            objectGraph = ObjectGraph.create(rootModule);
        }
        else
        {
            objectGraph = objectGraph.plus(rootModule);
        }

        // Inject statics
        objectGraph.injectStatics();

    }

    public void init(final Object rootModule, final Object target)
    {
        init(rootModule);
        inject(target);
    }

    public void inject(final Object target)
    {
        objectGraph.inject(target);
    }

    public <T> T resolve(Class<T> type)
    {
        return objectGraph.get(type);
    }
}

3.) Створіть RootModuleдля з'єднання своїх майбутніх модулів. Зверніть увагу, що ви повинні включити, injectsщоб вказати кожен клас, у якому ви будете використовувати @Injectанотацію, бо в іншому випадку кинджал кидає RuntimeException.

@Module(
    includes = {
        UtilsModule.class,
        NetworkingModule.class
    },
    injects = {
        MainActivity.class
    }
)
public class RootModule
{
}

4.) Якщо у вас є інші підмодулі у ваших модулях, вказаних у вашому Root, створіть для них модулі:

@Module(
    includes = {
        SerializerModule.class,
        CertUtilModule.class
    }
)
public class UtilsModule
{
}

5.) створити модулі листків, які отримують залежності як параметри конструктора. У моєму випадку не було кругової залежності, тож я не знаю, чи може Даггер це вирішити, але я вважаю це малоймовірним. Параметри конструктора також повинні бути надані у модулі від Dagger, якщо ви вказали, complete = falseвін може бути і в інших модулях.

@Module(complete = false, library = true)
public class NetworkingModule
{
    @Provides
    public ClientAuthAuthenticator providesClientAuthAuthenticator()
    {
        return new ClientAuthAuthenticator();
    }

    @Provides
    public ClientCertWebRequestor providesClientCertWebRequestor(ClientAuthAuthenticator clientAuthAuthenticator)
    {
        return new ClientCertWebRequestor(clientAuthAuthenticator);
    }

    @Provides
    public ServerCommunicator providesServerCommunicator(ClientCertWebRequestor clientCertWebRequestor)
    {
        return new ServerCommunicator(clientCertWebRequestor);
    }
}

6.) Розширити Applicationта ініціалізувати Injector.

@Override
public void onCreate()
{
    super.onCreate();
    Injector.INSTANCE.init(new RootModule());
}

7.) У своєму MainActivityвиклику інжектора в onCreate()методі.

@Override
protected void onCreate(Bundle savedInstanceState)
{
    Injector.INSTANCE.inject(this);
    super.onCreate(savedInstanceState);
    ...

8.) Використовуйте @Injectу своєму MainActivity.

public class MainActivity extends ActionBarActivity
{  
    @Inject
    public ServerCommunicator serverCommunicator;

...

Якщо ви отримаєте помилку no injectable constructor found, переконайтеся, що ви не забули @Providesпримітки.


Частково ця відповідь ґрунтується на коді, створеному Android Bootstrap. Отже, кредити їм за те, щоб розібратися. Рішення використовує Dagger v1.2.2.
EpicPandaForce

3
Обсяг цього dagger-compilerповинен бути, providedінакше він буде включений у додаток, і це під ліцензією GPL.
Денис Княжев

@deniskniazhev о, я цього не знав! Дякую за голову вгору!
EpicPandaForce
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.