¿Cómo configurar la inyección de dependencia DAGGER desde cero en un proyecto de Android?

100

¿Cómo usar Dagger? ¿Cómo configurar Dagger para que funcione en mi proyecto de Android?

Me gustaría usar Dagger en mi proyecto de Android, pero lo encuentro confuso.

EDITAR: Dagger2 también está disponible desde 2015 04 15, ¡y es aún más confuso!

[Esta pregunta es un "trozo" en el que estoy agregando a mi respuesta a medida que aprendí más sobre Dagger1 y aprendí más sobre Dagger2. Esta pregunta es más una guía que una "pregunta".]

EpicPandaForce
fuente
Ver también: stackoverflow.com/a/40546157/2413303
EpicPandaForce
Gracias por compartir esto. ¿Tiene conocimientos sobre cómo inyectar clases de ViewModel? Mi clase ViewModel no tiene @AssistedInject pero tiene dependencias que puede proporcionar el gráfico Dagger.
AndroidDev
1
Claro, consulte stackoverflow.com/questions/60884402/…
EpicPandaForce
Una pregunta más, con Dagger2, ¿es posible tener un objeto y su referencia es compartida por ViewModely PageKeyedDataSource? Como uso RxJava2, y quiero que CompositeDisposable sea compartido por ambas clases y si el usuario presiona el botón Atrás, quiero borrar el objeto Disposable. He agregado el caso aquí: stackoverflow.com/questions/62595956/…
AndroidDev
Es mejor colocar el compositeDisposable adentro ViewModely tal vez pasar el mismo compositeDisposable como argumento de constructor de su PageKeyedDataSource personalizado, pero realmente no usaría Dagger para esa parte porque entonces necesita subcomponentes subcopiados, y Hilt realmente no lo respaldará fácil para ti.
EpicPandaForce

Respuestas:

193

Guía para Dagger 2.x (Edición revisada 6) :

Los pasos son los siguientes:

1.) agregar Daggera sus build.gradlearchivos:

  • build.gradle de nivel superior :

.

// 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()
    }
}
  • nivel de aplicación 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.) Cree su AppContextModuleclase que proporciona las dependencias.

@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.) crea la AppContextComponentclase que proporciona la interfaz para obtener las clases que son inyectables.

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

3.1.) Así es como crearía un módulo con una implementación:

@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);
}

Tenga cuidado: debe proporcionar la @Scopeanotación (como @Singletono @ActivityScope) en el @Providesmétodo anotado del módulo para obtener un proveedor con ámbito dentro de su componente generado; de lo contrario, no tendrá ámbito y obtendrá una nueva instancia cada vez que inyecte.

3.2.) Cree un componente de ámbito de aplicación que especifique lo que puede inyectar (esto es lo mismo que injects={MainActivity.class}en 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.) Para las dependencias que puede crear a través de un constructor usted mismo y no querrá redefinir usando un @Module(por ejemplo, usa build flavors en su lugar para cambiar el tipo de implementación), puede usar un @Injectconstructor anotado.

public class Something {
    OtherThing otherThing;

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

Además, si usa el @Injectconstructor, puede usar la inyección de campo sin tener que llamar explícitamente component.inject(this):

public class Something {
    @Inject
    OtherThing otherThing;

    @Inject
    public Something() {
    }
}

Estas @Injectclases de constructor se agregan automáticamente al componente del mismo ámbito sin tener que especificarlas explícitamente en un módulo.

Una clase de constructor con @Singletonámbito @Injectse verá en @Singletoncomponentes con ámbito.

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

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

3.4.) Después de haber definido una implementación específica para una interfaz determinada, así:

public interface Something {
    void doSomething();
}

@Singleton
public class SomethingImpl {
    @Inject
    AnotherThing anotherThing;

    @Inject
    public SomethingImpl() {
    }
}

Deberá "vincular" la implementación específica a la interfaz con un @Module.

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

Un resumen de esto desde Dagger 2.4 es el siguiente:

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

4.) cree una Injectorclase para manejar su componente de nivel de aplicación (reemplaza al monolítico ObjectGraph)

(nota: Rebuild Projectpara crear la DaggerApplicationComponentclase de constructor usando 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.) crea tu CustomApplicationclase

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

6.) agregar CustomApplicationa su AndroidManifest.xml.

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

7.) Inyecta tus clases enMainActivity

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.) ¡Disfruta!

+1.) Puede especificar Scopepara sus componentes con los que puede crear componentes con ámbito de nivel de actividad . Los subámbitos le permiten proporcionar dependencias que solo necesita para un subámbito determinado, en lugar de para toda la aplicación. Normalmente, cada actividad tiene su propio módulo con esta configuración. Tenga en cuenta que existe un proveedor de ámbito por componente , lo que significa que para retener la instancia para esa actividad, el componente en sí debe sobrevivir al cambio de configuración. Por ejemplo, podría sobrevivir a través de onRetainCustomNonConfigurationInstance()un alcance de mortero.

Para obtener más información sobre el subámbito, consulte la guía de Google . También consulte este sitio sobre métodos de provisión y también la sección de dependencias de componentes ) y aquí .

Para crear un alcance personalizado, debe especificar la anotación de calificador de alcance:

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

Para crear un subámbito, debe especificar el ámbito de su componente y especificarlo ApplicationComponentcomo su dependencia. Obviamente, también debe especificar el subámbito en los métodos del proveedor del módulo.

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

    void inject(YourScopedClass scopedClass);
}

Y

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

Tenga en cuenta que sólo un componente restringidos se puede especificar como una dependencia. Piense en ello exactamente como en Java no se admite la herencia múltiple.

+2.) Acerca de @Subcomponent: esencialmente, un ámbito @Subcomponentpuede reemplazar la dependencia de un componente; pero en lugar de utilizar un constructor proporcionado por el procesador de anotaciones, necesitaría utilizar un método de fábrica de componentes.

Así que esto:

@Singleton
@Component
public interface ApplicationComponent {
}

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

    void inject(YourScopedClass scopedClass);
}

Se convierte en esto:

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

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

Y esto:

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

Se convierte en esto:

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

+3.): Consulte también otras preguntas de Stack Overflow sobre Dagger2, ya que proporcionan mucha información. Por ejemplo, mi estructura actual de Dagger2 se especifica en esta respuesta .

Gracias

Gracias por las guías de Github , TutsPlus , Joe Steele , Froger MCS y Google .

También para esta guía de migración paso a paso que encontré después de escribir esta publicación.

Y para la explicación del alcance de Kirill.

Aún más información en la documentación oficial .

EpicPandaForce
fuente
Creo que nos falta la implementación de DaggerApplicationComponent
Thanasis Kapelonis
1
@ThanasisKapelonis DaggerApplicationComponentes autogenerado por APT en la compilación, pero lo agregaré .
EpicPandaForce
1
Solo tuve que hacer público el método Injector.initializeApplicationComponent ya que mi CustomApplication estaba fuera del alcance del paquete y ¡todo funciona perfectamente! ¡Gracias!
Juan Saravia
2
Un poco tarde, pero tal vez los siguientes ejemplos ayuden a cualquiera: github.com/dawidgdanski/android-compass-api github.com/dawidgdanski/Bakery
dawid gdanski
1
Si aparece 'Advertencia: uso de complementos incompatibles para el procesamiento de anotaciones: android-apt. Esto puede resultar en un comportamiento inesperado. ' En el paso 1, cambie apt 'com.google.dagger: dagger-compiler: 2.7' a annotationProcessor 'com.google.dagger: dagger-compiler: 2.7' y elimine todos los apt config. Los detalles se pueden encontrar aquí bitbucket.org/hvisser/android-apt/wiki/Migration
thanhbinh84
11

Guía para Dagger 1.x :

Los pasos son los siguientes:

1.) agregar Daggeral build.gradlearchivo para las dependencias

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

Además, agregue packaging-optionpara evitar un error sobre duplicate APKs.

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

2.) crea una Injectorclase para manejar el 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.) Cree un RootModulepara vincular sus futuros módulos. Tenga en cuenta que debe incluir injectspara especificar cada clase en la que utilizará la @Injectanotación, porque de lo contrario Dagger lanza RuntimeException.

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

4.) En caso de que tenga otros submódulos dentro de sus módulos especificados en su raíz, cree módulos para esos:

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

5.) crea los módulos hoja que reciben las dependencias como parámetros del constructor. En mi caso, no hubo dependencia circular, así que no sé si Dagger puede resolver eso, pero lo encuentro poco probable. Los parámetros del constructor también deben proporcionarse en un módulo por Dagger, si lo especifica complete = false, también puede estar en otros módulos.

@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.) Amplíe Applicatione inicialice el archivo Injector.

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

7.) En su MainActivity, llame al Inyector en el onCreate()método.

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

8.) Úselo @Injecten su MainActivity.

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

...

Si recibe el error no injectable constructor found, asegúrese de no olvidar las @Providesanotaciones.

EpicPandaForce
fuente
Esta respuesta se basa en parte en el código generado por Android Bootstrap. Entonces, les damos crédito por resolverlo. Usos de solución Dagger v1.2.2.
EpicPandaForce
3
El alcance de dagger-compilerdebería ser de lo providedcontrario, se incluirá en la aplicación y está bajo licencia GPL.
Denis Kniazhev
@deniskniazhev ¡oh, no sabía eso! ¡Gracias por el aviso!
EpicPandaForce