Como configurar a injeção de dependência DAGGER do zero no projeto Android?

100

Como usar o punhal? Como configurar o Dagger para funcionar no meu projeto Android?

Eu gostaria de usar o Dagger em meu projeto Android, mas acho confuso.

EDIT: Dagger2 também está disponível desde 2015 04 15, e é ainda mais confuso!

[Esta pergunta é um "esboço" que estou adicionando à minha resposta à medida que aprendo mais sobre Dagger1 e aprendo mais sobre Dagger2. Esta questão é mais um guia do que uma "questão".]

EpicPandaForce
fonte
Consulte também: stackoverflow.com/a/40546157/2413303
EpicPandaForce
Obrigado por compartilhar isso. Você tem conhecimento sobre como injetar classes ViewModel? Minha classe ViewModel não tem nenhum @AssistedInject, mas tem dependências que podem ser fornecidas pelo gráfico Dagger.
AndroidDev
1
Claro, consulte stackoverflow.com/questions/60884402/…
EpicPandaForce
Mais uma pergunta, com Dagger2, é possível ter um objeto e sua referência é compartilhada por ViewModele PageKeyedDataSource? Como eu uso RxJava2 e quero que CompositeDisposable seja compartilhado por ambas as classes e se o usuário pressionar o botão Voltar, quero limpar o objeto Disposable. Eu adicionei um caso aqui: stackoverflow.com/questions/62595956/…
AndroidDev
É melhor você colocar o compositeDisposable dentro ViewModele talvez passar o mesmo compositeDisposable como o argumento do construtor do seu PageKeyedDataSource personalizado, mas eu não usaria Dagger para essa parte porque você precisa de subcomponentes sub-escopo, e o Hilt não fará suporte para isso fácil para você.
EpicPandaForce

Respostas:

193

Guia para Dagger 2.x (edição revisada 6) :

As etapas são as seguintes:

1.) adicionar Daggeraos seus build.gradlearquivos:

  • build.gradle de nível 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()
    }
}
  • nível de aplicativo 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.) Crie sua AppContextModuleclasse que fornece as dependências.

@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.) criar a AppContextComponentclasse que fornece a interface para obter as classes que são injetáveis.

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

3.1.) É assim que você criaria um módulo com uma implementação:

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

Cuidado: Você precisa fornecer a @Scopeanotação (como @Singletonou @ActivityScope) no @Providesmétodo anotado do módulo para obter um provedor com escopo dentro do componente gerado, caso contrário, ele ficará sem escopo e você obterá uma nova instância cada vez que injetar.

3.2.) Crie um componente com escopo de aplicativo que especifique o que você pode injetar (é o mesmo injects={MainActivity.class}em 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 dependências que você mesmo pode criar através de um construtor e não quer redefinir usando um @Module(por exemplo, você usa tipos de compilação para alterar o tipo de implementação), você pode usar o @Injectconstrutor anotado.

public class Something {
    OtherThing otherThing;

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

Além disso, se você usar o @Injectconstrutor, poderá usar a injeção de campo sem ter que chamar explicitamente component.inject(this):

public class Something {
    @Inject
    OtherThing otherThing;

    @Inject
    public Something() {
    }
}

Essas @Injectclasses construtoras são adicionadas automaticamente ao componente do mesmo escopo, sem a necessidade de especificá-las explicitamente em um módulo.

Uma classe de construtor com @Singletonescopo definido @Injectserá vista nos @Singletoncomponentes com escopo definido.

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

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

3.4.) Depois de definir uma implementação específica para uma determinada interface, como:

public interface Something {
    void doSomething();
}

@Singleton
public class SomethingImpl {
    @Inject
    AnotherThing anotherThing;

    @Inject
    public SomethingImpl() {
    }
}

Você precisará "vincular" a implementação específica à interface com um @Module.

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

Uma abreviação para isso, uma vez que Dagger 2.4, é o seguinte:

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

4.) crie uma Injectorclasse para lidar com seu componente de nível de aplicativo (substitui o monolítico ObjectGraph)

(nota: Rebuild Projectpara criar a DaggerApplicationComponentclasse builder 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.) crie sua CustomApplicationclasse

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

6.) adicionar CustomApplicationao seu AndroidManifest.xml.

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

7.) Injetar suas aulas emMainActivity

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.) Aproveite!

+1.) Você pode especificar Scopeseus componentes com os quais pode criar componentes com escopo no nível de atividade . Os subescopos permitem fornecer dependências que você precisa apenas para um determinado subescopo, em vez de em todo o aplicativo. Normalmente, cada atividade obtém seu próprio módulo com esta configuração. Observe que existe um provedor com escopo por componente , ou seja, para reter a instância dessa atividade, o próprio componente deve sobreviver à mudança de configuração. Por exemplo, ele pode sobreviver através de onRetainCustomNonConfigurationInstance(), ou uma mira de morteiro.

Para mais informações sobre subcopagem, confira o guia do Google . Consulte também este site sobre métodos de provisionamento e também a seção de dependências de componentes e aqui .

Para criar um escopo personalizado, você deve especificar a anotação do qualificador de escopo:

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

Para criar um subescopo, você precisa especificar o escopo em seu componente e especificar ApplicationComponentcomo sua dependência. Obviamente, você também precisa especificar o subescopo nos métodos do provedor de módulo.

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

    void inject(YourScopedClass scopedClass);
}

E

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

Observe que apenas um componente com escopo pode ser especificado como dependência. Pense nisso exatamente como a herança múltipla não é suportada em Java.

+2.) Sobre @Subcomponent: essencialmente, um escopo @Subcomponentpode substituir uma dependência de componente; mas em vez de usar um construtor fornecido pelo processador de anotação, você precisaria usar um método de fábrica de componentes.

Então, é isso:

@Singleton
@Component
public interface ApplicationComponent {
}

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

    void inject(YourScopedClass scopedClass);
}

Torna-se isto:

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

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

E isto:

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

Torna-se isto:

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

+3.): Por favor, verifique outras questões sobre Stack Overflow relacionadas a Dagger2 também, elas fornecem muitas informações. Por exemplo, minha estrutura Dagger2 atual é especificada nesta resposta .

obrigado

Obrigado pelos guias do Github , TutsPlus , Joe Steele , Froger MCS e Google .

Também para este guia de migração passo a passo que encontrei depois de escrever este post.

E para explicação de escopo por Kirill.

Ainda mais informações na documentação oficial .

EpicPandaForce
fonte
Acredito que estamos perdendo a implementação de DaggerApplicationComponent
Thanasis Kapelonis
1
@ThanasisKapelonis DaggerApplicationComponenté gerado automaticamente pelo APT na construção, mas vou adicioná-lo.
EpicPandaForce
1
Eu só tive que tornar público o método Injector.initializeApplicationComponent porque meu CustomApplication estava fora do escopo do pacote e tudo funciona perfeitamente! Obrigado!
Juan Saravia
2
Um pouco tarde, mas talvez os exemplos a seguir ajudem alguém: github.com/dawidgdanski/android-compass-api github.com/dawidgdanski/Bakery
dawid gdanski
1
Se você receber 'Aviso: usando plug-ins incompatíveis para o processamento de anotação: android-apt. Isso pode resultar em um comportamento inesperado. ' Na etapa 1, altere apt 'com.google.dagger: dagger-compiler: 2.7' para annotationProcessor 'com.google.dagger: dagger-compiler: 2.7' e remova todas as configurações do apt. Os detalhes podem ser encontrados aqui bitbucket.org/hvisser/android-apt/wiki/Migration
thanhbinh84
11

Guia para Dagger 1.x :

As etapas são as seguintes:

1.) adicionar Daggerao build.gradlearquivo para as dependências

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

Além disso, adicione packaging-optionpara evitar um erro 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.) criar uma Injectorclasse para lidar com o 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.) Crie um RootModulepara vincular seus módulos futuros. Por favor, note que você deve incluir injectspara especificar cada classe em que você usará @Injectanotações, caso contrário, Dagger lança RuntimeException.

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

4.) No caso de você ter outros submódulos dentro de seus módulos especificados em seu Root, crie módulos para estes:

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

5.) criar os módulos folha que recebem as dependências como parâmetros do construtor. No meu caso, não havia dependência circular, então não sei se Dagger pode resolver isso, mas acho improvável. Os parâmetros do construtor também devem ser fornecidos em um Módulo por Dagger; se você especificar complete = false, ele também pode ser fornecido em outros 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.) Estenda Applicatione inicialize o Injector.

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

7.) Em seu MainActivity, chame o Injetor no onCreate()método.

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

8.) Use @Injectem seu MainActivity.

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

...

Se você receber o erro no injectable constructor found, certifique-se de não esquecer as @Providesanotações.

EpicPandaForce
fonte
Essa resposta é parcialmente baseada no código gerado por Android Bootstrap. Portanto, os créditos a eles por descobrirem isso. Usos de solução Dagger v1.2.2.
EpicPandaForce de
3
O escopo de dagger-compilerdeve ser provideddiferente, ele será incluído no aplicativo e está sob licença GPL.
Denis Kniazhev de
@deniskniazhev oh, eu não sabia disso! Obrigado pelo aviso!
EpicPandaForce de