Wie richte ich die DAGGER-Abhängigkeitsinjektion in einem Android-Projekt von Grund auf neu ein?

100

Wie benutzt man Dolch? Wie konfiguriere ich Dagger für die Arbeit in meinem Android-Projekt?

Ich würde Dagger gerne in meinem Android-Projekt verwenden, finde es aber verwirrend.

EDIT: Dagger2 ist auch seit 2015 04 15 raus und es ist noch verwirrender!

[Diese Frage ist ein "Stub", den ich zu meiner Antwort hinzufüge, wenn ich mehr über Dagger1 und Dagger2 erfahren habe. Diese Frage ist eher ein Leitfaden als eine "Frage".]

EpicPandaForce
quelle
Siehe auch: stackoverflow.com/a/40546157/2413303
EpicPandaForce
Vielen Dank für das Teilen. Haben Sie Kenntnisse darüber, wie Sie ViewModel-Klassen einfügen können? Meine ViewModel-Klasse ist ohne @AssistedInject, hat aber Abhängigkeiten, die von Dagger graph bereitgestellt werden können?
AndroidDev
1
Sicher, siehe stackoverflow.com/questions/60884402/…
EpicPandaForce
Noch eine Frage: Ist es mit Dagger2 möglich, ein Objekt zu haben, dessen Referenz von ViewModelund geteilt wird PageKeyedDataSource? Wie ich RxJava2 verwende und möchte, dass CompositeDisposable von beiden Klassen gemeinsam genutzt wird, und wenn der Benutzer die Zurück-Taste drückt, möchte ich das Disposable-Objekt löschen. Ich habe hier einen zusätzlichen Fall hinzugefügt: stackoverflow.com/questions/62595956/…
AndroidDev
Sie sollten das CompositeDisposable besser in das Innere ViewModeleinfügen und möglicherweise dasselbe CompositeDisposable als Konstruktorargument Ihrer benutzerdefinierten PageKeyedDataSource übergeben, aber ich würde Dagger für diesen Teil nicht wirklich verwenden, da Sie dann Subkomponenten mit Unterbereich benötigen, und Hilt wird dies nicht wirklich unterstützen einfach für dich.
EpicPandaForce

Antworten:

193

Anleitung für Dolch 2.x (überarbeitete Ausgabe 6) :

Die Schritte sind die folgenden:

1.)Dagger zu deinen build.gradleDateien hinzufügen :

  • top level 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 auf App-Ebene :

.

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.) Erstellen Sie Ihre AppContextModuleKlasse, die die Abhängigkeiten bereitstellt.

@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.) Erstellen Sie die AppContextComponentKlasse, die die Schnittstelle zum Abrufen der injizierbaren Klassen bereitstellt.

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

3.1.) So würden Sie ein Modul mit einer Implementierung erstellen:

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

Achtung :: Sie müssen die @ScopeAnnotation (wie @Singletonoder @ActivityScope) für die mit Annotationen @Providesversehene Methode des Moduls angeben, um einen Anbieter mit Gültigkeitsbereich innerhalb Ihrer generierten Komponente zu erhalten. Andernfalls wird die Annotation aufgehoben und Sie erhalten bei jeder Injektion eine neue Instanz.

3.2.) Erstellen Sie eine Komponente mit Anwendungsbereich, die angibt, was Sie injizieren können (dies entspricht der injects={MainActivity.class}in Dolch 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.) Für Abhängigkeiten, die Sie selbst über einen Konstruktor erstellen können und die Sie nicht mit einem neu definieren möchten @Module(z. B. verwenden Sie stattdessen Build-Varianten, um den Implementierungstyp zu ändern), können Sie einen @Injectkommentierten Konstruktor verwenden.

public class Something {
    OtherThing otherThing;

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

Wenn Sie den @InjectKonstruktor verwenden, können Sie auch die Feldinjektion verwenden, ohne explizit Folgendes aufrufen zu müssen component.inject(this):

public class Something {
    @Inject
    OtherThing otherThing;

    @Inject
    public Something() {
    }
}

Diese @InjectKonstruktorklassen werden automatisch zur Komponente desselben Bereichs hinzugefügt, ohne dass sie explizit in einem Modul angegeben werden müssen.

Eine Konstruktorklasse mit @SingletonGültigkeitsbereich @Injectwird in @SingletonKomponenten mit Gültigkeitsbereich angezeigt.

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

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

3.4.) Nachdem Sie eine bestimmte Implementierung für eine bestimmte Schnittstelle definiert haben, gehen Sie wie folgt vor:

public interface Something {
    void doSomething();
}

@Singleton
public class SomethingImpl {
    @Inject
    AnotherThing anotherThing;

    @Inject
    public SomethingImpl() {
    }
}

Sie müssen die spezifische Implementierung mit a an die Schnittstelle "binden" @Module.

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

Eine Abkürzung dafür seit Dolch 2.4 ist die folgende:

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

4.) Erstellen Sie eine InjectorKlasse für Ihre Komponente auf Anwendungsebene (sie ersetzt die monolithische ObjectGraph).

(Hinweis: Rebuild ProjectErstellen der DaggerApplicationComponentBuilder-Klasse mit 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.) Erstelle deine CustomApplicationKlasse

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

6.)CustomApplication zu Ihrem hinzufügen AndroidManifest.xml.

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

7.) Injizieren Sie Ihre Klassen inMainActivity

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.) Viel Spaß!

+1.) Sie können Scopefür Ihre Komponenten angeben, mit denen Sie Komponenten mit Aktivitätsbereich erstellen können . Mit Unterbereichen können Sie Abhängigkeiten bereitstellen, die Sie nur für einen bestimmten Unterbereich und nicht für die gesamte Anwendung benötigen. In der Regel erhält jede Aktivität mit diesem Setup ein eigenes Modul. Beachten Sie, dass pro Komponente ein Anbieter mit Gültigkeitsbereich vorhanden ist. Um die Instanz für diese Aktivität beizubehalten, muss die Komponente selbst die Konfigurationsänderung überstehen. Zum Beispiel könnte es durch überleben onRetainCustomNonConfigurationInstance()oder ein Mörserfernrohr.

Weitere Informationen zum Subscoping finden Sie in der Anleitung von Google . Auch finden Sie diese Seite über Bereitstellung Methoden und auch die Komponentenabhängigkeiten Abschnitt ) und hier .

Um einen benutzerdefinierten Bereich zu erstellen, müssen Sie die Anmerkung zum Bereichsqualifizierer angeben:

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

Um einen Unterbereich zu erstellen, müssen Sie den Bereich für Ihre Komponente angeben und ApplicationComponentals Abhängigkeit angeben . Natürlich müssen Sie den Unterbereich auch in den Modulanbietermethoden angeben.

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

    void inject(YourScopedClass scopedClass);
}

Und

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

Bitte beachten Sie, dass nur eine Komponente mit Gültigkeitsbereich als Abhängigkeit angegeben werden kann. Stellen Sie sich das genau so vor, wie Mehrfachvererbung in Java nicht unterstützt wird.

+2.) Info @Subcomponent: Im Wesentlichen kann ein Gültigkeitsbereich @Subcomponenteine Komponentenabhängigkeit ersetzen. Anstatt jedoch einen vom Anmerkungsprozessor bereitgestellten Builder zu verwenden, müssten Sie eine Komponentenfactory-Methode verwenden.

Also das:

@Singleton
@Component
public interface ApplicationComponent {
}

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

    void inject(YourScopedClass scopedClass);
}

Wird dies:

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

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

Und das:

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

Wird dies:

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

+3.): Bitte überprüfen Sie auch andere Fragen zum Stapelüberlauf in Bezug auf Dagger2. Sie enthalten viele Informationen. Zum Beispiel ist meine aktuelle Dagger2-Struktur in dieser Antwort angegeben .

Vielen Dank

Vielen Dank für die Anleitungen bei Github , TutsPlus , Joe Steele , Froger MCS und Google .

Auch für diese Schritt-für-Schritt-Anleitung zur Migration habe ich nach dem Schreiben dieses Beitrags gefunden.

Und zur Erklärung des Umfangs durch Kirill.

Noch mehr Informationen in der offiziellen Dokumentation .

EpicPandaForce
quelle
Ich glaube, wir vermissen die Implementierung von DaggerApplicationComponent
Thanasis Kapelonis
1
@ThanasisKapelonis DaggerApplicationComponentwird von APT beim Erstellen automatisch generiert , aber ich werde es hinzufügen.
EpicPandaForce
1
Ich musste nur die Methode Injector.initializeApplicationComponent veröffentlichen, da meine CustomApplication außerhalb des Paketbereichs lag und alles perfekt funktioniert! Vielen Dank!
Juan Saravia
2
Ein bisschen spät, aber vielleicht helfen die folgenden Beispiele jedem: github.com/dawidgdanski/android-compass-api github.com/dawidgdanski/Bakery
dawid gdanski
1
Wenn Sie 'Warnung: Verwenden inkompatibler Plugins für die Annotationsverarbeitung: android-apt. Dies kann zu einem unerwarteten Verhalten führen. ' Ändern Sie in Schritt 1 apt 'com.google.dagger: dagger-compiler: 2.7' in annotationProcessor 'com.google.dagger: dagger-compiler: 2.7' und entfernen Sie alle apt-Konfigurationen. Die Details finden Sie hier bitbucket.org/hvisser/android-apt/wiki/Migration
thanhbinh84
11

Anleitung für Dolch 1.x :

Die Schritte sind die folgenden:

1.) DaggerZur build.gradleDatei für die Abhängigkeiten hinzufügen

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

Fügen Sie außerdem hinzu packaging-option, um einen Fehler zu vermeiden duplicate APKs.

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

2.) Erstellen Sie eine InjectorKlasse, um die zu behandeln 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.) Erstellen Sie eine RootModule, um Ihre zukünftigen Module miteinander zu verknüpfen. Bitte beachten Sie, dass Sie injectsangeben müssen , um jede Klasse anzugeben, in der Sie @InjectAnmerkungen verwenden, da sonst Dagger wirft RuntimeException.

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

4.) Wenn Sie andere Untermodule in Ihren Modulen haben, die in Ihrem Root angegeben sind, erstellen Sie Module für diese:

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

5.) Erstellen Sie die Blattmodule, die die Abhängigkeiten als Konstruktorparameter erhalten. In meinem Fall gab es keine zirkuläre Abhängigkeit, daher weiß ich nicht, ob Dagger das lösen kann, aber ich finde es unwahrscheinlich. Die Konstruktorparameter müssen auch in einem Modul von Dagger bereitgestellt werden. Wenn Sie dies angeben complete = false, können sie auch in anderen Modulen enthalten sein.

@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.) Erweitern Applicationund initialisieren Sie die Injector.

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

7.) MainActivityRufen Sie in Ihrem den Injector in der onCreate()Methode auf.

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

8.) Verwenden Sie @Injectin Ihrem MainActivity.

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

...

Wenn Sie den Fehler erhalten no injectable constructor found, stellen Sie sicher, dass Sie die @ProvidesAnmerkungen nicht vergessen haben .

EpicPandaForce
quelle
Diese Antwort basiert teilweise auf dem von generierten Code Android Bootstrap. Also, danke ihnen dafür, dass sie es herausgefunden haben. Lösung verwendet Dagger v1.2.2.
EpicPandaForce
3
Der Umfang von dagger-compilersollte sein, providedsonst wird es in die Anwendung aufgenommen und steht unter der GPL-Lizenz.
Denis Kniazhev
@deniskniazhev oh, das wusste ich nicht! Danke für die Warnung!
EpicPandaForce