จะตั้งค่า DAGGER dependency injection ตั้งแต่เริ่มต้นในโครงการ Android ได้อย่างไร


100

วิธีใช้ Dagger? วิธีกำหนดค่า Dagger ให้ทำงานในโครงการ Android ของฉัน

ฉันต้องการใช้ Dagger ในโปรเจ็กต์ Android ของฉัน แต่ฉันรู้สึกสับสน

แก้ไข: Dagger2 ออกตั้งแต่ 2015 04 15 ด้วยและมันก็ยิ่งสับสน!

[คำถามนี้เป็น "ต้นขั้ว" ที่ฉันเพิ่มเข้าไปในคำตอบเมื่อได้เรียนรู้เพิ่มเติมเกี่ยวกับ Dagger1 และเรียนรู้เพิ่มเติมเกี่ยวกับ Dagger2 คำถามนี้เป็นแนวทางมากกว่า "คำถาม"]


ดูเพิ่มเติมที่: stackoverflow.com/a/40546157/2413303
EpicPandaForce

ขอบคุณสำหรับการแบ่งปันสิ่งนี้ คุณมีความรู้เกี่ยวกับวิธีการฉีดคลาส ViewModel หรือไม่? คลาส ViewModel ของฉันไม่มี @AssistedInject ใด ๆ แต่มีการอ้างอิงที่สามารถจัดเตรียมโดย Dagger graph?
AndroidDev

1
ได้แน่นอนดูstackoverflow.com/questions/60884402/…
EpicPandaForce

อีกหนึ่งคำถามเกี่ยวกับ Dagger2 เป็นไปได้หรือไม่ที่จะมีวัตถุและข้อมูลอ้างอิงถูกแชร์โดยViewModelและPageKeyedDataSource? เช่นเดียวกับที่ฉันใช้ RxJava2 และต้องการให้ CompositeDisposable แชร์โดยทั้งสองคลาสและหากผู้ใช้กดปุ่มย้อนกลับฉันต้องการล้างวัตถุทิ้ง ฉันได้เพิ่มกรณีที่นี่: stackoverflow.com/questions/62595956/…
AndroidDev

คุณดีกว่าที่จะใส่ compositeDisposable ไว้ข้างในViewModelและอาจส่ง compositeDisposable เดียวกันเป็นอาร์กิวเมนต์ตัวสร้างของ 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.)สำหรับการอ้างอิงที่คุณสามารถสร้างผ่านตัวสร้างด้วยตัวเองและไม่ต้องการกำหนดใหม่โดยใช้ a @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()หรือขอบเขตปูน

สำหรับข้อมูลเพิ่มเติมเกี่ยว subscoping ตรวจสอบคู่มือที่ 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.):โปรดตรวจสอบคำถาม Stack Overflow อื่น ๆ เกี่ยวกับ Dagger2 ด้วยเช่นกันพวกเขาให้ข้อมูลมากมาย ตัวอย่างเช่นโครงสร้าง Dagger2 ปัจจุบันของฉันถูกระบุไว้ในคำตอบนี้

ขอบคุณ

ขอบคุณสำหรับคำแนะนำที่Github , TutsPlus , โจสตีล , Froger MCSและGoogle

นอกจากนี้สำหรับคำแนะนำการย้ายทีละขั้นตอนนี้ฉันพบหลังจากเขียนโพสต์นี้

และสำหรับคำอธิบายขอบเขตโดย Kirill

ข้อมูลเพิ่มเติมในเอกสารอย่างเป็นทางการ


ฉันเชื่อว่าเราขาดการใช้งาน DaggerApplicationComponent
Thanasis Kapelonis

1
@ThanasisKapelonis DaggerApplicationComponentสร้างอัตโนมัติโดย APT ในการสร้าง แต่ฉันจะเพิ่ม
EpicPandaForce

1
ฉันต้องเปิดเผยวิธีการ Injector.initializeApplicationComponent ต่อสาธารณะเนื่องจาก CustomApplication ของฉันอยู่นอกขอบเขตแพ็คเกจและทุกอย่างทำงานได้สมบูรณ์แบบ! ขอบคุณ!
Juan Saravia

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

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-optionduplicate 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การระบุระดับที่คุณจะใช้ทุกคำอธิบายประกอบเพราะมิฉะนั้นกริชพ่น@InjectRuntimeException

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

4. ) ในกรณีที่คุณมีโมดูลย่อยอื่น ๆ ภายในโมดูลของคุณที่ระบุไว้ในรูทของคุณให้สร้างโมดูลสำหรับ:

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

5. ) สร้างโมดูลลีฟที่รับการอ้างอิงเป็นพารามิเตอร์ตัวสร้าง ในกรณีของฉันไม่มีการพึ่งพาแบบวงกลมดังนั้นฉันไม่รู้ว่า Dagger สามารถแก้ไขปัญหานั้นได้หรือไม่ แต่ฉันคิดว่ามันไม่น่าเป็นไปได้ พารามิเตอร์ตัวสร้างจะต้องถูกจัดเตรียมไว้ใน Module by 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เรียกหัวฉีดใน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คำอธิบายประกอบ


Android Bootstrapคำตอบนี้ส่วนหนึ่งขึ้นอยู่กับรหัสที่สร้างขึ้นโดย ดังนั้นให้เครดิตกับพวกเขาในการหาคำตอบ Dagger v1.2.2การใช้วิธีการแก้ปัญหา
EpicPandaForce

3
ขอบเขตของdagger-compilerควรเป็นprovidedอย่างอื่นจะรวมอยู่ในแอปพลิเคชันและอยู่ภายใต้ใบอนุญาต GPL
Denis Kniazhev

@deniskniazhev โอ้ฉันไม่รู้! ขอบคุณสำหรับหัวขึ้น!
EpicPandaForce
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.