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

100

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

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

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

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

EpicPandaForce
источник
См. Также: stackoverflow.com/a/40546157/2413303
EpicPandaForce
Спасибо, что поделились этим. У вас есть знания о том, как вводить классы ViewModel? В моем классе ViewModel нет @AssistedInject, но есть зависимости, которые могут быть предоставлены графом Dagger?
AndroidDev
1
Конечно, см stackoverflow.com/questions/60884402/...
EpicPandaForce
Еще один вопрос, с Dagger2, возможно ли иметь объект, и ссылка на него разделяется ViewModelи PageKeyedDataSource? Как я использую RxJava2 и хочу, чтобы CompositeDisposable совместно использовался обоими классами, и если пользователь нажимает кнопку возврата, я хочу очистить объект Disposable. Я добавил сюда случай: stackoverflow.com/questions/62595956/…
AndroidDev
Вам лучше поместить композитныйDisposable внутрь ViewModelи, возможно, передать тот же композитDisposable в качестве аргумента конструктора вашего настраиваемого 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() {
    }
}

Вам нужно будет «привязать» конкретную реализацию к интерфейсу с помощью @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 .

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

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

Еще больше информации в официальной документации .

EpicPandaForce
источник
Я считаю, что нам не хватает реализации DaggerApplicationComponent
Танасис Капелонис
1
@ThanasisKapelonis автоматически создается DaggerApplicationComponentAPT при сборке, но я добавлю его.
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-compiler: 2.7' на annotationProcessor 'com.google.dagger: dagger-compiler: 2.7' и удалите все apt config. Подробности можно найти здесь bitbucket.org/hvisser/android-apt/wiki/Migration
thanhbinh84 05
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аннотацию, потому что в противном случае 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вызовите Injector 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аннотации.

оборота EpicPandaForce
источник
Этот ответ частично основан на коде, созданном с помощью Android Bootstrap. Итак, спасибо им за то, что они это выяснили. Решение использует Dagger v1.2.2.
EpicPandaForce
3
Объем dagger-compilerдолжен бытьprovided иначе он будет включен в приложение, и это под лицензией GPL.
Денис Княжев
@deniskniazhev ох, я этого не знал! Спасибо за внимание!
EpicPandaForce