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

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

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

EDIT: Dagger2 также выходит с 2015 года 04 15, и это еще более запутанно!

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

Ответ 1

Руководство для Кинжала 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} в кинжале 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 автоматически добавляются к компоненту той же области действия без необходимости явно указывать их в модуле.

A @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 может заменить зависимость компонента; но вместо использования построителя, созданного процессором аннотации, вам нужно будет использовать компонент factory.

Итак, это:

@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.

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

И для пояснения по видам Кирилла.

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

Ответ 2

Руководство для Кинжал 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.) Если у вас есть другие подмодули внутри ваших модулей, указанных в корне, создайте для них модули:

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

5.) создать листовые модули, которые получают зависимости как параметры конструктора. В моем случае не было круговой зависимости, поэтому я не знаю, может ли Кинжал разрешить это, но я нахожу это маловероятным. Параметры конструктора также должны предоставляться в модуле с помощью кинжала, если вы укажете 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.