Как настроить внедрение зависимостей DAGGER с нуля в Android-проекте?

Как использовать кинжал? Как настроить Dagger для работы в моем Android-проекте?

Я бы хотел использовать Dagger в своем Android-проекте, но это меня сбивает.

РЕДАКТИРОВАТЬ: Dagger2 также отсутствует с 2015 04 15, и это еще более сбивает с толку!

[Этот вопрос представляет собой «заглушку», которую я добавляю к своему ответу, поскольку я узнал больше о Dagger1 и больше о Dagger2. Этот вопрос является скорее руководством, чем "вопросом".]


person EpicPandaForce    schedule 20.11.2014    source источник
comment
См. Также: stackoverflow.com/a/40546157/2413303   -  person EpicPandaForce    schedule 11.11.2016
comment
Спасибо, что поделились этим. У вас есть знания о том, как вводить классы ViewModel? В моем классе ViewModel нет @AssistedInject, но есть зависимости, которые могут быть предоставлены графом Dagger?   -  person AndroidDev    schedule 24.06.2020
comment
Конечно, см. stackoverflow.com/questions/60884402/   -  person EpicPandaForce    schedule 24.06.2020
comment
Еще один вопрос, с Dagger2, возможно ли иметь объект, ссылка на который используется ViewModel и PageKeyedDataSource? Как я использую RxJava2 и хочу, чтобы CompositeDisposable совместно использовался обоими классами, и если пользователь нажимает кнопку возврата, я хочу очистить объект Disposable. Я добавил здесь случай: stackoverflow .com / questions / 62595956 /   -  person AndroidDev    schedule 26.06.2020
comment
Вам лучше поместить композитныйDisposable внутрь ViewModel и, возможно, передать тот же композитныйDisposable в качестве аргумента конструктора вашего настраиваемого PageKeyedDataSource, но я бы не стал использовать Dagger для этой части, потому что тогда вам понадобятся подкомпоненты с подзаданной областью, а Hilt на самом деле не будет поддерживать это так просто для тебя.   -  person EpicPandaForce    schedule 26.06.2020
comment
Вы предлагаете мне обработать эту часть обычным способом, а не использовать Dagger и реализовать полный граф DI? Извините, я загрязняю эту страницу. Но когда я использую Dagger2, возникает множество вопросов. Также ответьте на мой вопрос, упомянутый выше, чтобы любой, кто сталкивается с такой же проблемой, мог задавать вопросы там.   -  person AndroidDev    schedule 26.06.2020


Ответы (2)


Руководство для 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() {
    }
}

Вам нужно будет «привязать» конкретную реализацию к интерфейсу с помощью @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.) Пожалуйста, ответьте на другие вопросы Stack Overflow, касающиеся Dagger2, они предоставляют много информации. Например, моя текущая структура Dagger2 указана в этом ответе.

Спасибо

Благодарим вас за руководства на Github, TutsPlus, Джо Стил, Froger MCS и Google.

Также для этого пошагового руководства по миграции, которое я нашел после написания этого сообщения.

И за объяснение объема Кирилла.

Дополнительную информацию можно найти в официальной документации.

person Community    schedule 29.04.2015
comment
Я считаю, что нам не хватает реализации DaggerApplicationComponent - person Thanasis Kapelonis; 06.05.2015
comment
@ThanasisKapelonis DaggerApplicationComponent автоматически создается APT при сборке, но я добавлю его. - person EpicPandaForce; 06.05.2015
comment
Я пересмотрел руководство, чтобы добавить зависимости компонентов и сделать материал более понятным. - person EpicPandaForce; 18.05.2015
comment
Мне просто нужно было обнародовать метод Injector.initializeApplicationComponent, поскольку мое CustomApplication находилось за пределами пакета, и все работает отлично! Спасибо! - person Juan Saravia; 28.05.2015
comment
Немного поздно, но, возможно, следующие примеры помогут кому угодно: github.com/dawidgdanski/android-compass- api github.com/dawidgdanski/Bakery - person dawid gdanski; 06.11.2015
comment
любой, кто читает это и все еще думает, что фреймворк для внедрения зависимостей - хорошая идея для Android, не в своем уме. Простой заводской шаблон меньше кода и в 1000 раз проще для понимания. - person enl8enmentnow; 12.04.2016
comment
@ enl8enmentnow Нет, закачка в поле довольно приятная. В какой-то момент мне придется добавить в руководство использование @Inject public Something() и @Singleton public class Something, учитывая, что если вы не хотите переопределять класс, вы можете автоматически сгенерировать его с помощью Dagger2, не создавая их в модуле. Я этого еще не добавил. Честно говоря, модуль - это просто навороченная фабрика. Компонент является фабричным преобразователем графа зависимостей. - person EpicPandaForce; 12.04.2016
comment
Парсер аннотаций обладает огромным потенциалом. Я думаю, вы просто царапаете поверхность. Возможно, вы используете аннотации вставки вместо более простого фабричного шаблона, который вызывает путаницу. Спасибо! - person enl8enmentnow; 12.04.2016
comment
@ enl8enment Теперь, когда вы упомянули об этом, вы можете использовать Component как фабрику. Так работают методы предоставления. - person EpicPandaForce; 13.04.2016
comment
@EpicPandaForce есть конкретная причина, по которой вы не аннотировали поставщиков AppContextModule с помощью @Singleton? - person Daksh; 15.06.2016
comment
@Daksh, разница в том, что добавление @Singleton создает поставщика с ограниченной областью действия (ScopedProvider.create(AppModule_JobManagerFactory.create(builder.appModule));), а не добавление @Singleton просто создает поставщика с незаданной областью (AppModule_PackageManagerFactory.create(builder.appModule);). Фабрика по умолчанию вызывает метод провайдера в модуле (module.packageManager();), а метод с ограниченной областью действия добавляет вокруг себя блокирующий synchronized(this) блок с двойной проверкой. Поэтому, если возвращаемое значение такое же, в этой блокировке нет необходимости. - person EpicPandaForce; 15.06.2016
comment
Если вы получили сообщение «Предупреждение: использование несовместимых плагинов для обработки аннотаций»: android-apt. Это может привести к неожиданному поведению. ' На шаге 1 измените apt 'com.google.dagger: dagger-compiler: 2.7' на annotationProcessor 'com.google.dagger: dagger-compiler: 2.7' и удалите все apt config. Подробности можно найти здесь bitbucket.org/hvisser/android-apt/wiki/Migration < / а> - person thanhbinh84; 05.07.2017

Руководство для 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, потому что в противном случае Dagger выбрасывает 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 решить эту проблему, но считаю это маловероятным. Параметры конструктора также должны быть предоставлены в модуле от 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 аннотации.

person Community    schedule 20.11.2014
comment
Этот ответ частично основан на коде, сгенерированном Android Bootstrap. Итак, спасибо им за то, что они это выяснили. Решение использует Dagger v1.2.2. - person EpicPandaForce; 20.11.2014
comment
Объем dagger-compiler должен быть provided, иначе он будет включен в приложение и под лицензией GPL. - person Denis Kniazhev; 10.06.2015
comment
@deniskniazhev ой, я этого не знал! Спасибо за внимание! - person EpicPandaForce; 10.06.2015