Android uygulamalarında Dagger kullanma

Dagger ile ilgili temel bilgiler sayfasında Dagger'ın uygulamanızdaki bağımlılık ekleme işlemini otomatikleştirmenize nasıl yardımcı olabileceği açıklandı. Dagger ile sıkıcı ve hataya yatkın ortak kod yazmanız gerekmez.

En iyi uygulamaların özeti

  • Dagger grafiğine mümkün olduğunda tür eklemek için @Inject ile kurucu yerleştirme özelliğini kullanın. Aksi halde:
    • Dagger'a bir arayüzde hangi uygulamanın bulunması gerektiğini bildirmek için @Binds kullanın.
    • Dagger'a projenizin sahibi olmadığı sınıfları nasıl sağlayacağını belirtmek için @Provides hizmetini kullanın.
  • Bir bileşende modülleri yalnızca bir kez bildirmeniz gerekir.
  • Kapsam ek açıklamalarını, ek açıklamanın kullanıldığı kullanım ömrüne göre adlandırın. Örnek olarak @ApplicationScope, @LoggedUserScope ve @ActivityScope verilebilir.

Bağımlılık ekleme

Projenizde Dagger'ı kullanmak için bu bağımlılıkları build.gradle dosyanızdaki uygulamanıza ekleyin. Dagger'ın en son sürümünü bu GitHub projesinde bulabilirsiniz.

Kotlin

plugins {
  id 'kotlin-kapt'
}

dependencies {
    implementation 'com.google.dagger:dagger:2.x'
    kapt 'com.google.dagger:dagger-compiler:2.x'
}

Java

dependencies {
    implementation 'com.google.dagger:dagger:2.x'
    annotationProcessor 'com.google.dagger:dagger-compiler:2.x'
}

Android'de Dagger

Şekil 1'deki bağımlılık grafiğini içeren bir Android uygulaması örneği düşünün.

Giriş Etkinliği, UserRepository'ye bağlı olan SignViewModel'e bağlıdır. UserRepository, UserLocalDataSource ve UserRemoteDataSource'a bağlıdır. UserRepository ise Retrofit'e bağlıdır.

Şekil 1. Örnek kodun bağımlılık grafiği

Android'de, grafik örneğinin uygulama çalıştığı sürece bellekte kalmasını istediğinizden genellikle uygulama sınıfınızda bulunan bir Dagger grafiği oluşturursunuz. Bu şekilde grafik, uygulama yaşam döngüsüne eklenir. Bazı durumlarda, uygulama bağlamını grafikte sunmak isteyebilirsiniz. Bunun için grafiğin Application sınıfında olması da gerekir. Bu yaklaşımın bir avantajı, grafiğin diğer Android çerçeve sınıflarında da kullanılabilmesidir. Ayrıca, testlerde özel bir Application sınıfı kullanmanıza olanak tanıyarak test sürecini basitleştirir.

Grafiği oluşturan arayüz @Component ile ek açıklamaya sahip olduğundan, bu arayüzü ApplicationComponent veya ApplicationGraph olarak adlandırabilirsiniz. Aşağıdaki kod snippet'inde gösterildiği gibi, genellikle bu bileşenin bir örneğini özel Application sınıfınızda tutar ve uygulama grafiğine her ihtiyacınız olduğunda bu bileşeni çağırırsınız:

Kotlin

// Definition of the Application graph
@Component
interface ApplicationComponent { ... }

// appComponent lives in the Application class to share its lifecycle
class MyApplication: Application() {
    // Reference to the application graph that is used across the whole app
    val appComponent = DaggerApplicationComponent.create()
}

Java

// Definition of the Application graph
@Component
public interface ApplicationComponent {
}

// appComponent lives in the Application class to share its lifecycle
public class MyApplication extends Application {

    // Reference to the application graph that is used across the whole app
    ApplicationComponent appComponent = DaggerApplicationComponent.create();
}

Etkinlikler ve parçalar gibi belirli Android çerçeve sınıfları sistem tarafından sağlandığından, Dagger bunları sizin için oluşturamaz. Özellikle etkinlikler için tüm başlatma kodlarının onCreate() yöntemine geçmesi gerekir. Bu, @Inject ek açıklamasını, önceki örneklerde yaptığınız gibi sınıfın (yapıcı yerleştirme) oluşturucuda kullanamayacağınız anlamına gelir. Bunun yerine, alan yerleştirme yöntemini kullanmanız gerekir.

onCreate() yönteminde bir etkinliğin gerektirdiği bağımlılıkları oluşturmak yerine Dagger'ın bu bağımlılıkları sizin için doldurmasını istersiniz. Alan yerleştirme için bunun yerine, Dagger grafiğinden almak istediğiniz alanlara @Inject ek açıklamasını uygularsınız.

Kotlin

class LoginActivity: Activity() {
    // You want Dagger to provide an instance of LoginViewModel from the graph
    @Inject lateinit var loginViewModel: LoginViewModel
}

Java

public class LoginActivity extends Activity {

    // You want Dagger to provide an instance of LoginViewModel from the graph
    @Inject
    LoginViewModel loginViewModel;
}

Basitlik sağlaması açısından LoginViewModel, Android Mimari Bileşenleri ViewModel değildir, ViewModel görevi gören normal bir sınıftır. Bu sınıfların nasıl ekleneceği hakkında daha fazla bilgi edinmek için dev-dagger dalındaki resmi Android Blueprints Dagger uygulamasında yer alan koda göz atın.

Dagger ile ilgili dikkat edilmesi gereken noktalardan biri, yerleştirilen alanların gizli olamayacağıdır. Önceki kodda olduğu gibi, en az bir paket-özel görünürlüğe sahip olmaları gerekir.

Etkinlik ekleme

Dagger'ın, gerektirdiği ViewModel için LoginActivity grafiğe erişmesi gerektiğini bilmelidir. Dagger ile ilgili temel bilgiler sayfasında, işlevleri grafikten almak istediğiniz işleve sahip işlevleri açığa çıkararak grafikten nesne almak için @Component arayüzünü kullandınız. Bu durumda, Dagger'a bağımlı eklenmesi gereken bir nesne (bu örnekte LoginActivity) hakkında bilgi vermeniz gerekir. Bunun için, yerleştirme isteğinde bulunan nesneyi parametre olarak alan bir işlevi gösterirsiniz.

Kotlin

@Component
interface ApplicationComponent {
    // This tells Dagger that LoginActivity requests injection so the graph needs to
    // satisfy all the dependencies of the fields that LoginActivity is requesting.
    fun inject(activity: LoginActivity)
}

Java

@Component
public interface ApplicationComponent {
    // This tells Dagger that LoginActivity requests injection so the graph needs to
    // satisfy all the dependencies of the fields that LoginActivity is injecting.
    void inject(LoginActivity loginActivity);
}

Bu işlev Dagger'a LoginActivity grafiğe erişmek istediğini ve ekleme isteğinde bulunduğunu söylüyor. Dagger, LoginActivity için gereken tüm bağımlılıkları (LoginViewModel kendi bağımlılıkları ile) karşılamalıdır. Yerleştirme isteğinde bulunan birden fazla sınıfınız varsa bunların tümünü bileşende tam türleriyle özel olarak belirtmeniz gerekir. Örneğin, LoginActivity ve RegistrationActivity yerleştirme isteğinde bulunduysanız her iki durumu da kapsayan genel bir yöntem yerine iki inject() yönteminiz olur. Genel bir inject() yöntemi, Dagger'a neyin sağlanması gerektiğini söylemez. Arayüzdeki işlevler herhangi bir ada sahip olabilir ancak Dagger'da, parametre olarak eklenecek nesneyi aldıklarında bunları inject() olarak adlandırmak bir kuraldır.

Etkinliğe bir nesne eklemek için Application sınıfınızda tanımlanan appComponent yöntemini kullanır ve inject() yöntemini çağırarak yerleştirme isteğinde bulunan etkinliğin bir örneğini geçirirsiniz.

Etkinlikleri kullanırken, parça geri yüklemeyle ilgili sorunları önlemek için super.onCreate() çağrısı yapmadan önce etkinliğin onCreate() yöntemine Dagger'ı ekleyin. super.onCreate() uygulamasındaki geri yükleme aşamasında bir etkinlik, etkinlik bağlamalarına erişmek isteyebilecek parçalar ekler.

Parça kullanırken Dagger'ı parçanın onAttach() yöntemine ekleyin. Bu durumda, bu işlem super.onAttach() çağrısından önce veya sonra yapılabilir.

Kotlin

class LoginActivity: Activity() {
    // You want Dagger to provide an instance of LoginViewModel from the graph
    @Inject lateinit var loginViewModel: LoginViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        // Make Dagger instantiate @Inject fields in LoginActivity
        (applicationContext as MyApplication).appComponent.inject(this)
        // Now loginViewModel is available

        super.onCreate(savedInstanceState)
    }
}

// @Inject tells Dagger how to create instances of LoginViewModel
class LoginViewModel @Inject constructor(
    private val userRepository: UserRepository
) { ... }

Java

public class LoginActivity extends Activity {

    // You want Dagger to provide an instance of LoginViewModel from the graph
    @Inject
    LoginViewModel loginViewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // Make Dagger instantiate @Inject fields in LoginActivity
        ((MyApplication) getApplicationContext()).appComponent.inject(this);
        // Now loginViewModel is available

        super.onCreate(savedInstanceState);
    }
}

public class LoginViewModel {

    private final UserRepository userRepository;

    // @Inject tells Dagger how to create instances of LoginViewModel
    @Inject
    public LoginViewModel(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}

Dagger'a grafiği oluşturmak için geri kalan bağımlılıkları nasıl sağlaması gerektiğini anlatalım:

Kotlin

class UserRepository @Inject constructor(
    private val localDataSource: UserLocalDataSource,
    private val remoteDataSource: UserRemoteDataSource
) { ... }

class UserLocalDataSource @Inject constructor() { ... }
class UserRemoteDataSource @Inject constructor(
    private val loginService: LoginRetrofitService
) { ... }

Java

public class UserRepository {

    private final UserLocalDataSource userLocalDataSource;
    private final UserRemoteDataSource userRemoteDataSource;

    @Inject
    public UserRepository(UserLocalDataSource userLocalDataSource, UserRemoteDataSource userRemoteDataSource) {
        this.userLocalDataSource = userLocalDataSource;
        this.userRemoteDataSource = userRemoteDataSource;
    }
}

public class UserLocalDataSource {

    @Inject
    public UserLocalDataSource() {}
}

public class UserRemoteDataSource {

    private final LoginRetrofitService loginRetrofitService;

    @Inject
    public UserRemoteDataSource(LoginRetrofitService loginRetrofitService) {
        this.loginRetrofitService = loginRetrofitService;
    }
}

Dagger modülleri

Bu örnekte, Retrofit ağ iletişimi kitaplığını kullanıyorsunuz. UserRemoteDataSource, LoginRetrofitService üzerinde bir bağımlılığa sahip. Ancak LoginRetrofitService örneği oluşturma yöntemi, şu ana kadar yaptıklarınızdan farklıdır. Bu bir sınıf örnekleme değildir. Giriş hizmetini yapılandırmak için Retrofit.Builder() çağrılıp farklı parametrelerin iletilmesinden kaynaklanır.

Dagger'a sınıf örneğinin nasıl sağlanacağını bildirmenin @Inject ek açıklaması dışında başka bir yolu da vardır: Dagger modüllerinin içindeki bilgiler. Dagger modülü, @Module ek açıklamasına sahip bir sınıftır. Burada, @Provides ek açıklamasıyla bağımlılıkları tanımlayabilirsiniz.

Kotlin

// @Module informs Dagger that this class is a Dagger Module
@Module
class NetworkModule {

    // @Provides tell Dagger how to create instances of the type that this function
    // returns (i.e. LoginRetrofitService).
    // Function parameters are the dependencies of this type.
    @Provides
    fun provideLoginRetrofitService(): LoginRetrofitService {
        // Whenever Dagger needs to provide an instance of type LoginRetrofitService,
        // this code (the one inside the @Provides method) is run.
        return Retrofit.Builder()
                .baseUrl("https://example.com")
                .build()
                .create(LoginService::class.java)
    }
}

Java

// @Module informs Dagger that this class is a Dagger Module
@Module
public class NetworkModule {

    // @Provides tell Dagger how to create instances of the type that this function
    // returns (i.e. LoginRetrofitService).
    // Function parameters are the dependencies of this type.
    @Provides
    public LoginRetrofitService provideLoginRetrofitService() {
        // Whenever Dagger needs to provide an instance of type LoginRetrofitService,
        // this code (the one inside the @Provides method) is run.
        return new Retrofit.Builder()
                .baseUrl("https://example.com")
                .build()
                .create(LoginService.class);
    }
}

Modüller, nesnelerin nasıl sağlanacağıyla ilgili bilgileri anlamsal olarak kapsüllemenin bir yoludur. Gördüğünüz gibi, ağla ilgili nesneleri sağlama mantığını gruplandırmak için NetworkModule sınıfını çağırdınız. Uygulama genişletilirse buradan OkHttpClient sağlama veya Gson ya da Moshi'yi yapılandırma işlemlerini de ekleyebilirsiniz.

Bir @Provides yönteminin bağımlılıkları, söz konusu yöntemin parametreleridir. Önceki yöntemde, yöntemde parametre olmadığı için LoginRetrofitService herhangi bir bağımlılık olmadan sağlanabilir. Parametre olarak bir OkHttpClient tanımlamış olsaydınız Dagger'ın LoginRetrofitService bağımlılıklarını karşılamak için grafikten bir OkHttpClient örneği sağlaması gerekirdi. Örneğin:

Kotlin

@Module
class NetworkModule {
    // Hypothetical dependency on LoginRetrofitService
    @Provides
    fun provideLoginRetrofitService(
        okHttpClient: OkHttpClient
    ): LoginRetrofitService { ... }
}

Java

@Module
public class NetworkModule {

    @Provides
    public LoginRetrofitService provideLoginRetrofitService(OkHttpClient okHttpClient) {
        ...
    }
}

Dagger grafiğinin bu modülü bilmesi için onu @Component arayüzüne aşağıdaki şekilde eklemeniz gerekir:

Kotlin

// The "modules" attribute in the @Component annotation tells Dagger what Modules
// to include when building the graph
@Component(modules = [NetworkModule::class])
interface ApplicationComponent {
    ...
}

Java

// The "modules" attribute in the @Component annotation tells Dagger what Modules
// to include when building the graph
@Component(modules = NetworkModule.class)
public interface ApplicationComponent {
    ...
}

Dagger grafiğine tür eklemenin önerilen yolu, kurucu ekleme yöntemini kullanmaktır (ör. sınıfın oluşturucusundaki @Inject ek açıklaması ile). Bazen bu mümkün değildir ve Dagger modüllerini kullanmanız gerekir. Dagger'ın bir nesnenin örneğini nasıl oluşturacağınızı belirlemek için hesaplamanın sonucunu kullanmasını istediğiniz durumlar buna örnek olarak verilebilir. Dagger, bu türden bir örnek sağlaması gerektiğinde kodu @Provides yöntemi içinde çalıştırır.

Örnekteki Dagger grafiğinin şu anda görünümü şöyledir:

Giriş Etkinliği bağımlılık grafiği şeması

2. Şekil. Dagger tarafından yerleştirilen LoginActivity ile grafiğin gösterimi

Grafiğin giriş noktası LoginActivity. LoginActivity, LoginViewModel eklediğinden Dagger, LoginViewModel örneğinin ve yinelemeli olarak bağımlılıklarının nasıl sağlanacağını bilen bir grafik oluşturur. Dagger, sınıf kurucusundaki @Inject ek açıklaması sayesinde bunun nasıl yapılacağını biliyor.

Dagger tarafından oluşturulan ApplicationComponent içinde, nasıl sağlanacağını bildiği tüm sınıfların örneklerini almak için fabrika türünde bir yöntem bulunur. Bu örnekte Dagger, LoginRetrofitService örneğini almak için ApplicationComponent kapsamındaki NetworkModule öğesine yetki verir.

Hançer dürbünleri

Kapsamlardan, bir bileşende türün benzersiz bir örneğine sahip olmanın bir yolu olarak Dagger ile ilgili temel bilgiler sayfasında bahsediliyordu. Bir türü, bileşenin yaşam döngüsüne dahil etmenin anlamı budur.

UserRepository uygulamasını, uygulamanın diğer özelliklerinde kullanmak ve her ihtiyacınız olduğunda yeni bir nesne oluşturmak istemeyebileceğiniz için bunu uygulamanın tamamı için benzersiz bir örnek olarak tanımlayabilirsiniz. LoginRetrofitService için de aynı durum söz konusudur: Oluşturması pahalı olabilir ve aynı zamanda bu nesnenin benzersiz bir örneğini yeniden kullanmak isteyebilirsiniz. UserRemoteDataSource örneği oluşturmak o kadar pahalı değildir. Bu nedenle, örneği bileşenin yaşam döngüsüne dahil etmek gerekli değildir.

@Singleton, javax.inject paketiyle birlikte gelen tek kapsam ek açıklamasıdır. Bu özelliği, ApplicationComponent ve uygulamanın tamamında yeniden kullanmak istediğiniz nesnelere not eklemek için kullanabilirsiniz.

Kotlin

@Singleton
@Component(modules = [NetworkModule::class])
interface ApplicationComponent {
    fun inject(activity: LoginActivity)
}

@Singleton
class UserRepository @Inject constructor(
    private val localDataSource: UserLocalDataSource,
    private val remoteDataSource: UserRemoteDataSource
) { ... }

@Module
class NetworkModule {
    // Way to scope types inside a Dagger Module
    @Singleton
    @Provides
    fun provideLoginRetrofitService(): LoginRetrofitService { ... }
}

Java

@Singleton
@Component(modules = NetworkModule.class)
public interface ApplicationComponent {
    void inject(LoginActivity loginActivity);
}

@Singleton
public class UserRepository {

    private final UserLocalDataSource userLocalDataSource;
    private final UserRemoteDataSource userRemoteDataSource;

    @Inject
    public UserRepository(UserLocalDataSource userLocalDataSource, UserRemoteDataSource userRemoteDataSource) {
        this.userLocalDataSource = userLocalDataSource;
        this.userRemoteDataSource = userRemoteDataSource;
    }
}

@Module
public class NetworkModule {

    @Singleton
    @Provides
    public LoginRetrofitService provideLoginRetrofitService() { ... }
}

Kapsamları nesnelere uygularken bellek sızıntılarına yol açmamaya dikkat edin. Kapsamlı bileşen bellekte olduğu sürece oluşturulan nesne de bellekte yer alır. ApplicationComponent, uygulama başlatıldığında (Application sınıfında) oluşturulduğundan, uygulama kaldırıldığında yok edilir. Bu nedenle, benzersiz UserRepository örneği, uygulama kaldırılana kadar her zaman bellekte kalır.

Dagger alt bileşenleri

Giriş akışınız (tek bir LoginActivity tarafından yönetilen) birden fazla parçadan oluşuyorsa tüm parçalarda aynı LoginViewModel örneğini yeniden kullanmanız gerekir. @Singleton, aşağıdaki nedenlerden dolayı örneği yeniden kullanmak için LoginViewModel uygulamasına ek açıklama ekleyemez:

  1. LoginViewModel örneği, akış sona erdikten sonra bellekte kalır.

  2. Her giriş akışı için farklı bir LoginViewModel örneği istersiniz. Örneğin, kullanıcı çıkış yaparsa kullanıcının ilk kez giriş yaptığı örnek yerine farklı bir LoginViewModel örneği istersiniz.

LoginViewModel kapsamını LoginActivity yaşam döngüsüne dahil etmek için giriş akışı için yeni bir bileşen (yeni bir alt grafik) ve yeni bir kapsam oluşturmanız gerekir.

Giriş akışına özgü bir grafik oluşturalım.

Kotlin

@Component
interface LoginComponent {}

Java

@Component
public interface LoginComponent {
}

Girişe özel bir yapılandırmaya sahip olduğundan, LoginActivity artık LoginComponent öğesinden yerleştirmeler almalıdır. Bu işlem, ApplicationComponent sınıfından LoginActivity ekleme sorumluluğunu ortadan kaldırır.

Kotlin

@Component
interface LoginComponent {
    fun inject(activity: LoginActivity)
}

Java

@Component
public interface LoginComponent {
    void inject(LoginActivity loginActivity);
}

LoginViewModel, UserRepository ürününe bağlı olduğundan LoginComponent, nesnelere ApplicationComponent üzerinden erişebilmelidir. Dagger'a yeni bir bileşenin başka bir bileşenin parçasını kullanmasını istediğinizi bildirmenin yolu Dagger alt bileşenleri ile göndermektir. Yeni bileşen, paylaşılan kaynakları içeren bileşenin alt bileşeni olmalıdır.

Alt bileşenler, bir üst bileşenin nesne grafiğini devralan ve genişleten bileşenlerdir. Dolayısıyla, üst bileşende sağlanan tüm nesneler alt bileşende de sağlanır. Bu şekilde, alt bileşendeki bir nesne, üst bileşen tarafından sağlanan bir nesneye bağlı olabilir.

Alt bileşenlerin örneklerini oluşturmak için üst bileşenin bir örneğine ihtiyacınız vardır. Bu nedenle, üst bileşen tarafından alt bileşene sağlanan nesneler yine üst bileşenin kapsamına alınır.

Örnekte, LoginComponent öğesini ApplicationComponent öğesinin alt bileşeni olarak tanımlamanız gerekir. Bunu yapmak için LoginComponent uygulamasına @Subcomponent ek açıklaması ekleyin:

Kotlin

// @Subcomponent annotation informs Dagger this interface is a Dagger Subcomponent
@Subcomponent
interface LoginComponent {

    // This tells Dagger that LoginActivity requests injection from LoginComponent
    // so that this subcomponent graph needs to satisfy all the dependencies of the
    // fields that LoginActivity is injecting
    fun inject(loginActivity: LoginActivity)
}

Java

// @Subcomponent annotation informs Dagger this interface is a Dagger Subcomponent
@Subcomponent
public interface LoginComponent {

    // This tells Dagger that LoginActivity requests injection from LoginComponent
    // so that this subcomponent graph needs to satisfy all the dependencies of the
    // fields that LoginActivity is injecting
    void inject(LoginActivity loginActivity);
}

ApplicationComponent tarafından LoginComponent örneklerinin nasıl oluşturulacağını bilmesi için LoginComponent içinde bir alt bileşen fabrikası da tanımlamanız gerekir.

Kotlin

@Subcomponent
interface LoginComponent {

    // Factory that is used to create instances of this subcomponent
    @Subcomponent.Factory
    interface Factory {
        fun create(): LoginComponent
    }

    fun inject(loginActivity: LoginActivity)
}

Java

@Subcomponent
public interface LoginComponent {

    // Factory that is used to create instances of this subcomponent
    @Subcomponent.Factory
    interface Factory {
        LoginComponent create();
    }

    void inject(LoginActivity loginActivity);
}

Dagger'a LoginComponent ürününün ApplicationComponent bileşeninin bir alt bileşeni olduğunu belirtmek için bunu aşağıdaki şekilde belirtmeniz gerekir:

  1. Alt bileşenin sınıfını ek açıklamanın subcomponents özelliğine ileten yeni bir Dagger modülü (ör. SubcomponentsModule) oluşturma.

    Kotlin

    // The "subcomponents" attribute in the @Module annotation tells Dagger what
    // Subcomponents are children of the Component this module is included in.
    @Module(subcomponents = LoginComponent::class)
    class SubcomponentsModule {}
    

    Java

    // The "subcomponents" attribute in the @Module annotation tells Dagger what
    // Subcomponents are children of the Component this module is included in.
    @Module(subcomponents = LoginComponent.class)
    public class SubcomponentsModule {
    }
    
  2. Yeni modülü (SubcomponentsModule) ApplicationComponent platformuna ekleme:

    Kotlin

    // Including SubcomponentsModule, tell ApplicationComponent that
    // LoginComponent is its subcomponent.
    @Singleton
    @Component(modules = [NetworkModule::class, SubcomponentsModule::class])
    interface ApplicationComponent {
    }
    

    Java

    // Including SubcomponentsModule, tell ApplicationComponent that
    // LoginComponent is its subcomponent.
    @Singleton
    @Component(modules = {NetworkModule.class, SubcomponentsModule.class})
    public interface ApplicationComponent {
    }
    

    Sorumluluk artık LoginComponent adlı kuruluşa ait olduğundan ApplicationComponent öğesinin artık LoginActivity öğesinin eklenmesine gerek olmadığını unutmayın. Bu nedenle, inject() yöntemini ApplicationComponent öğesinden kaldırabilirsiniz.

    ApplicationComponent kullanıcılarının, LoginComponent örneklerini nasıl oluşturacağınızı bilmesi gerekiyor. Üst bileşen, tüketicilerin üst bileşen örneğinden alt bileşenin örneklerini oluşturmasını sağlamak için arayüzüne bir yöntem eklemelidir:

  3. Arayüzde LoginComponent örnekleri oluşturan fabrikayı kullanıma sunun:

    Kotlin

    @Singleton
    @Component(modules = [NetworkModule::class, SubcomponentsModule::class])
    interface ApplicationComponent {
    // This function exposes the LoginComponent Factory out of the graph so consumers
    // can use it to obtain new instances of LoginComponent
    fun loginComponent(): LoginComponent.Factory
    }
    

    Java

    @Singleton
    @Component(modules = { NetworkModule.class, SubcomponentsModule.class} )
    public interface ApplicationComponent {
    // This function exposes the LoginComponent Factory out of the graph so consumers
    // can use it to obtain new instances of LoginComponent
    LoginComponent.Factory loginComponent();
    }
    

Alt bileşenlere kapsam atama

Projeyi oluşturursanız hem ApplicationComponent hem de LoginComponent örneği oluşturabilirsiniz. ApplicationComponent, uygulama bellekte olduğu sürece grafiğin aynı örneğini kullanmak istediğiniz için uygulamanın yaşam döngüsüne bağlıdır.

LoginComponent'in yaşam döngüsü nedir? LoginComponent öğesini kullanmanızın nedenlerinden biri, girişle ilgili parçalar arasında aynı LoginViewModel örneğini paylaşmanız gerekmesidir. Ancak yeni bir giriş akışı olduğunda farklı LoginViewModel örneklerini de istersiniz. LoginActivity, LoginComponent için doğru kullanım ömrüdür: Her yeni etkinlik için yeni bir LoginComponent örneğine ve bu LoginComponent örneğini kullanabilecek parçalara ihtiyacınız vardır.

LoginComponent, LoginActivity yaşam döngüsüne bağlı olduğundan Application sınıfında applicationComponent referansını tuttuğunuz şekilde etkinlikteki bileşene bir referans tutmanız gerekir. Bu şekilde, parçalar buna erişebilir.

Kotlin

class LoginActivity: Activity() {
    // Reference to the Login graph
    lateinit var loginComponent: LoginComponent
    ...
}

Java

public class LoginActivity extends Activity {

    // Reference to the Login graph
    LoginComponent loginComponent;

    ...
}

loginComponent değişkenine @Inject ifadesi eklenmediğinden, bu değişkenin Dagger tarafından sağlanmasını beklemediğinize dikkat edin.

LoginComponent referansı almak için ApplicationComponent öğesini kullanabilir ve ardından LoginActivity öğesini aşağıdaki gibi yerleştirebilirsiniz:

Kotlin

class LoginActivity: Activity() {
    // Reference to the Login graph
    lateinit var loginComponent: LoginComponent

    // Fields that need to be injected by the login graph
    @Inject lateinit var loginViewModel: LoginViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        // Creation of the login graph using the application graph
        loginComponent = (applicationContext as MyDaggerApplication)
                            .appComponent.loginComponent().create()

        // Make Dagger instantiate @Inject fields in LoginActivity
        loginComponent.inject(this)

        // Now loginViewModel is available

        super.onCreate(savedInstanceState)
    }
}

Java

public class LoginActivity extends Activity {

    // Reference to the Login graph
    LoginComponent loginComponent;

    // Fields that need to be injected by the login graph
    @Inject
    LoginViewModel loginViewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // Creation of the login graph using the application graph
        loginComponent = ((MyApplication) getApplicationContext())
                                .appComponent.loginComponent().create();

        // Make Dagger instantiate @Inject fields in LoginActivity
        loginComponent.inject(this);

        // Now loginViewModel is available

        super.onCreate(savedInstanceState);
    }
}

LoginComponent, etkinliğin onCreate() yönteminde oluşturulur ve etkinlik silindiğinde de dolaylı olarak imha edilir.

LoginComponent, her istendiğinde her zaman aynı LoginViewModel örneğini sağlamalıdır. Bunu, özel bir ek açıklama kapsamı oluşturup hem LoginComponent hem de LoginViewModel ek açıklamasıyla birlikte ekleyerek sağlayabilirsiniz. Üst bileşen tarafından zaten kullanıldığı ve nesnenin bir uygulama teklisi (uygulamanın tamamı için benzersiz bir örnek) haline geleceğinden @Singleton ek açıklamasını kullanamazsınız. Farklı bir ek açıklama kapsamı oluşturmanız gerekir.

Bu durumda, bu kapsamı @LoginScope olarak adlandırabilirsiniz ancak bu iyi bir uygulama değildir. Kapsam ek açıklamasının adı, yerine getirme amacı açısından açıkça belirtilmemelidir. Ek açıklamalar RegistrationComponent ve SettingsComponent gibi eşdüzey bileşenler tarafından yeniden kullanılabileceği için bunun yerine kullanım ömrüne bağlı olarak adlandırılmalıdır. Bu nedenle @LoginScope yerine @ActivityScope olarak adlandırmalısınız.

Kotlin

// Definition of a custom scope called ActivityScope
@Scope
@Retention(value = AnnotationRetention.RUNTIME)
annotation class ActivityScope

// Classes annotated with @ActivityScope are scoped to the graph and the same
// instance of that type is provided every time the type is requested.
@ActivityScope
@Subcomponent
interface LoginComponent { ... }

// A unique instance of LoginViewModel is provided in Components
// annotated with @ActivityScope
@ActivityScope
class LoginViewModel @Inject constructor(
    private val userRepository: UserRepository
) { ... }

Java

// Definition of a custom scope called ActivityScope
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface ActivityScope {}

// Classes annotated with @ActivityScope are scoped to the graph and the same
// instance of that type is provided every time the type is requested.
@ActivityScope
@Subcomponent
public interface LoginComponent { ... }

// A unique instance of LoginViewModel is provided in Components
// annotated with @ActivityScope
@ActivityScope
public class LoginViewModel {

    private final UserRepository userRepository;

    @Inject
    public LoginViewModel(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}

Şimdi, LoginViewModel gerektiren iki parçanız varsa her ikisi de aynı örnekle sağlanır. Örneğin, bir LoginUsernameFragment ve LoginPasswordFragment kartınız varsa bunların LoginComponent tarafından yerleştirilmesi gerekir:

Kotlin

@ActivityScope
@Subcomponent
interface LoginComponent {

    @Subcomponent.Factory
    interface Factory {
        fun create(): LoginComponent
    }

    // All LoginActivity, LoginUsernameFragment and LoginPasswordFragment
    // request injection from LoginComponent. The graph needs to satisfy
    // all the dependencies of the fields those classes are injecting
    fun inject(loginActivity: LoginActivity)
    fun inject(usernameFragment: LoginUsernameFragment)
    fun inject(passwordFragment: LoginPasswordFragment)
}

Java

@ActivityScope
@Subcomponent
public interface LoginComponent {

    @Subcomponent.Factory
    interface Factory {
        LoginComponent create();
    }

    // All LoginActivity, LoginUsernameFragment and LoginPasswordFragment
    // request injection from LoginComponent. The graph needs to satisfy
    // all the dependencies of the fields those classes are injecting
    void inject(LoginActivity loginActivity);
    void inject(LoginUsernameFragment loginUsernameFragment);
    void inject(LoginPasswordFragment loginPasswordFragment);
}

Bileşenler, LoginActivity nesnesinde bulunan bileşenin örneğine erişir. LoginUserNameFragment için örnek kod, aşağıdaki kod snippet'inde görünür:

Kotlin

class LoginUsernameFragment: Fragment() {

    // Fields that need to be injected by the login graph
    @Inject lateinit var loginViewModel: LoginViewModel

    override fun onAttach(context: Context) {
        super.onAttach(context)

        // Obtaining the login graph from LoginActivity and instantiate
        // the @Inject fields with objects from the graph
        (activity as LoginActivity).loginComponent.inject(this)

        // Now you can access loginViewModel here and onCreateView too
        // (shared instance with the Activity and the other Fragment)
    }
}

Java

public class LoginUsernameFragment extends Fragment {

    // Fields that need to be injected by the login graph
    @Inject
    LoginViewModel loginViewModel;

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);

        // Obtaining the login graph from LoginActivity and instantiate
        // the @Inject fields with objects from the graph
        ((LoginActivity) getActivity()).loginComponent.inject(this);

        // Now you can access loginViewModel here and onCreateView too
        // (shared instance with the Activity and the other Fragment)
    }
}

Aynı durum LoginPasswordFragment için de geçerlidir:

Kotlin

class LoginPasswordFragment: Fragment() {

    // Fields that need to be injected by the login graph
    @Inject lateinit var loginViewModel: LoginViewModel

    override fun onAttach(context: Context) {
        super.onAttach(context)

        (activity as LoginActivity).loginComponent.inject(this)

        // Now you can access loginViewModel here and onCreateView too
        // (shared instance with the Activity and the other Fragment)
    }
}

Java

public class LoginPasswordFragment extends Fragment {

    // Fields that need to be injected by the login graph
    @Inject
    LoginViewModel loginViewModel;

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);

        ((LoginActivity) getActivity()).loginComponent.inject(this);

        // Now you can access loginViewModel here and onCreateView too
        // (shared instance with the Activity and the other Fragment)
    }
}

Şekil 3'te Dagger grafiğinin yeni alt bileşenle nasıl göründüğü gösterilmektedir. Beyaz nokta içeren sınıflar (UserRepository, LoginRetrofitService ve LoginViewModel), ilgili bileşenlerinin kapsamına dahil edilen benzersiz bir örneğe sahip olan sınıflardır.

Son alt bileşen eklendikten sonra uygulama grafiği

3. Şekil. Android uygulaması örneği için oluşturduğunuz grafiğin temsili

Grafiğin bölümlerini parçalara ayıralım:

  1. NetworkModule (ve dolayısıyla LoginRetrofitService), bileşende belirttiğiniz için ApplicationComponent öğesine dahil edilir.

  2. UserRepository, ApplicationComponent kapsamında olduğu için ApplicationComponent klasöründe kalır. Proje büyürse aynı örneği farklı özelliklerde (ör. kayıt) paylaşmak istersiniz.

    UserRepository, ApplicationComponent öğesinin bir parçası olduğundan UserRepository örneklerini sağlayabilmek için bağımlılıklarının (ör. UserLocalDataSource ve UserRemoteDataSource) bu bileşende de olması gerekir.

  3. LoginViewModel, yalnızca LoginComponent tarafından yerleştirilen sınıflar tarafından gerekli olduğu için LoginComponent kapsamına dahil edildi. ApplicationComponent içinde bağımlılık olmadığından LoginViewModel, ApplicationComponent içine dahil edilmemiştir LoginViewModel.

    Benzer şekilde, UserRepository kapsamını ApplicationComponent öğesine ayarlamamış olsaydınız Dagger, şu anda UserRepository kullanılan tek yer olduğundan UserRepository ve bağımlılıklarını otomatik olarak LoginComponent kapsamına dahil ederdi.

Nesneleri farklı bir yaşam döngüsü için kapsama almaktan ayrı olarak, alt bileşenler oluşturmak, uygulamanızın farklı kısımlarını birbirlerinden kapsamak için iyi bir uygulamadır.

Uygulamanızı, uygulamanızın akışına bağlı olarak farklı Dagger alt grafikleri oluşturacak şekilde yapılandırmak bellek ve başlatma süresi açısından daha performanslı ve ölçeklenebilir bir uygulama elde etmenize yardımcı olur.

Dagger grafiği oluştururken kullanılabilecek en iyi uygulamalar

Uygulamanız için Dagger grafiğini oluştururken:

  • Bir bileşen oluşturduğunuzda, söz konusu bileşenin ömrü boyunca hangi öğenin sorumlu olduğunu göz önünde bulundurmalısınız. Bu durumda ApplicationComponent için Application sınıfı, LoginComponent hizmetinden LoginActivity sorumlu olur.

  • Kapsam oluşturmayı yalnızca mantıklı olduğunda kullanın. Kapsam oluşturmayı aşırı kullanmak, uygulamanızın çalışma zamanı performansını olumsuz yönde etkileyebilir: Bileşen bellekte olduğu ve kapsamlı bir nesneyi almak daha pahalı olduğu sürece nesne bellekte kalır. Dagger, nesneyi sağladığında fabrika türü sağlayıcı yerine DoubleCheck kilitlemesini kullanır.

Dagger kullanan bir projeyi test etme

Dagger gibi bağımlılık ekleme çerçevelerini kullanmanın avantajlarından biri, kodunuzu test etmeyi kolaylaştırmasıdır.

Birim testleri

Birim testleri için Dagger'ı kullanmanız gerekmez. Oluşturucu yerleştirme kullanan bir sınıfı test ederken bu sınıfı örneklendirmek için Dagger'ı kullanmanız gerekmez. Yapıcısını, ek açıklama eklenmediğinde olduğu gibi doğrudan sahte veya sahte bağımlılıklarda geçen şekilde çağırabilirsiniz.

Örneğin, LoginViewModel test edilirken:

Kotlin

@ActivityScope
class LoginViewModel @Inject constructor(
    private val userRepository: UserRepository
) { ... }

class LoginViewModelTest {

    @Test
    fun `Happy path`() {
        // You don't need Dagger to create an instance of LoginViewModel
        // You can pass a fake or mock UserRepository
        val viewModel = LoginViewModel(fakeUserRepository)
        assertEquals(...)
    }
}

Java

@ActivityScope
public class LoginViewModel {

    private final UserRepository userRepository;

    @Inject
    public LoginViewModel(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}

public class LoginViewModelTest {

    @Test
    public void happyPath() {
        // You don't need Dagger to create an instance of LoginViewModel
        // You can pass a fake or mock UserRepository
        LoginViewModel viewModel = new LoginViewModel(fakeUserRepository);
        assertEquals(...);
    }
}

Uçtan uca testler

Entegrasyon testleri için test amaçlı bir TestApplicationComponent oluşturmak iyi bir uygulamadır. Üretim ve test için farklı bir bileşen yapılandırması kullanılır.

Bu, uygulamanızda modüllerin daha önceden tasarlanmasını gerektirir. Test bileşeni, üretim bileşenini genişletir ve farklı bir modül grubu yükler.

Kotlin

// TestApplicationComponent extends from ApplicationComponent to have them both
// with the same interface methods. You need to include the modules of the
// component here as well, and you can replace the ones you want to override.
// This sample uses FakeNetworkModule instead of NetworkModule
@Singleton
@Component(modules = [FakeNetworkModule::class, SubcomponentsModule::class])
interface TestApplicationComponent : ApplicationComponent {
}

Java

// TestApplicationComponent extends from ApplicationComponent to have them both
// with the same interface methods. You need to include the modules of the
// Component here as well, and you can replace the ones you want to override.
// This sample uses FakeNetworkModule instead of NetworkModule
@Singleton
@Component(modules = {FakeNetworkModule.class, SubcomponentsModule.class})
public interface TestApplicationComponent extends ApplicationComponent {
}

FakeNetworkModule, orijinal NetworkModule yönergesini sahte bir şekilde kullanıyor. Burada, değiştirmek istediğiniz öğenin sahte örneklerini veya örneklerini sağlayabilirsiniz.

Kotlin

// In the FakeNetworkModule, pass a fake implementation of LoginRetrofitService
// that you can use in your tests.
@Module
class FakeNetworkModule {
    @Provides
    fun provideLoginRetrofitService(): LoginRetrofitService {
        return FakeLoginService()
    }
}

Java

// In the FakeNetworkModule, pass a fake implementation of LoginRetrofitService
// that you can use in your tests.
@Module
public class FakeNetworkModule {

    @Provides
    public LoginRetrofitService provideLoginRetrofitService() {
        return new FakeLoginService();
    }
}

Entegrasyon veya uçtan uca testlerinizde ApplicationComponent yerine TestApplicationComponent oluşturan bir TestApplication kullanırsınız.

Kotlin

// Your test application needs an instance of the test graph
class MyTestApplication: MyApplication() {
    override val appComponent = DaggerTestApplicationComponent.create()
}

Java

// Your test application needs an instance of the test graph
public class MyTestApplication extends MyApplication {
    ApplicationComponent appComponent = DaggerTestApplicationComponent.create();
}

Ardından bu test uygulaması, araç testlerini çalıştırmak için kullanacağınız özel bir TestRunner içinde kullanılır. Bu konuda daha fazla bilgi edinmek için Android uygulaması codelab'inizdeDagger'ı kullanma bölümüne göz atın.

Dagger modülleriyle çalışma

Dagger modülleri, nesnelerin semantik bir şekilde nasıl sağlanacağını ele almanın bir yoludur. Bileşenlere modüller ekleyebilirsiniz ancak diğer modüllerin içinde de modüller ekleyebilirsiniz. Bu güçlü bir özelliktir, ancak kolayca kötüye kullanılabilir.

Bir modül bir bileşene veya başka bir modüle eklendikten sonra, zaten Dagger grafiğinde yer alır. Dagger, bu nesneleri bu bileşende sağlayabilir. Bir modül eklemeden önce, modülün Dagger grafiğinde yer alıp almadığını kontrol edin. Bunun için modülün bileşene önceden eklenip eklenmediğini kontrol edebilir ya da projeyi derleyip Dagger'ın bu modül için gerekli bağımlılıkları bulup bulamadığını kontrol edebilirsiniz.

İyi uygulama, modüllerin bir bileşende yalnızca bir kez tanımlanmasını gerektirir (belirli gelişmiş Dagger kullanım alanları dışında).

Grafiğinizi bu şekilde yapılandırdığınızı varsayalım. ApplicationComponent, Module1 ve Module2 içerirken Module1, ModuleX içerir.

Kotlin

@Component(modules = [Module1::class, Module2::class])
interface ApplicationComponent { ... }

@Module(includes = [ModuleX::class])
class Module1 { ... }

@Module
class Module2 { ... }

Java

@Component(modules = {Module1.class, Module2.class})
public interface ApplicationComponent { ... }

@Module(includes = {ModuleX.class})
public class Module1 { ... }

@Module
public class Module2 { ... }

Şimdi ise Module2, ModuleX tarafından sağlanan sınıflara bağlıdır. ModuleX, aşağıdaki kod snippet'inde görüldüğü gibi grafiğe iki kez dahil edildiği için Module2 öğesine ModuleX dahil edilmesi kötü uygulama kapsamına girer:

Kotlin

// Bad practice: ModuleX is declared multiple times in this Dagger graph
@Component(modules = [Module1::class, Module2::class])
interface ApplicationComponent { ... }

@Module(includes = [ModuleX::class])
class Module1 { ... }

@Module(includes = [ModuleX::class])
class Module2 { ... }

Java

// Bad practice: ModuleX is declared multiple times in this Dagger graph.
@Component(modules = {Module1.class, Module2.class})
public interface ApplicationComponent { ... }

@Module(includes = ModuleX.class)
public class Module1 { ... }

@Module(includes = ModuleX.class)
public class Module2 { ... }

Bunun yerine, aşağıdakilerden birini yapmanız gerekir:

  1. Modülleri yeniden düzenleyin ve ortak modülü bileşene çıkarın.
  2. Her iki modülün de paylaştığı nesnelerle yeni bir modül oluşturun ve modülü bileşene çıkarın.

Bu şekilde yeniden düzenleme yapılmaması, net bir organizasyon yapısı olmadan birbirini içeren çok sayıda modülün ortaya çıkmasına neden olur ve her bağımlılığın nereden geldiğini görmeyi zorlaştırır.

İyi uygulama (1. Seçenek): ModuleX bir kez Dagger grafiğinde tanımlanmaktadır.

Kotlin

@Component(modules = [Module1::class, Module2::class, ModuleX::class])
interface ApplicationComponent { ... }

@Module
class Module1 { ... }

@Module
class Module2 { ... }

Java

@Component(modules = {Module1.class, Module2.class, ModuleX.class})
public interface ApplicationComponent { ... }

@Module
public class Module1 { ... }

@Module
public class Module2 { ... }

İyi uygulama (2. seçenek): ModuleX bölgesinde Module1 ve Module2 kaynaklı yaygın bağımlılıklar, bileşende yer alan ModuleXCommon adlı yeni bir modüle çıkarılır. Ardından her bir modüle özgü bağımlılıklarla ModuleXWithModule1Dependencies ve ModuleXWithModule2Dependencies adlı iki modül daha oluşturulur. Tüm modüller Dagger grafiğinde bir kez tanımlanır.

Kotlin

@Component(modules = [Module1::class, Module2::class, ModuleXCommon::class])
interface ApplicationComponent { ... }

@Module
class ModuleXCommon { ... }

@Module
class ModuleXWithModule1SpecificDependencies { ... }

@Module
class ModuleXWithModule2SpecificDependencies { ... }

@Module(includes = [ModuleXWithModule1SpecificDependencies::class])
class Module1 { ... }

@Module(includes = [ModuleXWithModule2SpecificDependencies::class])
class Module2 { ... }

Java

@Component(modules = {Module1.class, Module2.class, ModuleXCommon.class})
public interface ApplicationComponent { ... }

@Module
public class ModuleXCommon { ... }

@Module
public class ModuleXWithModule1SpecificDependencies { ... }

@Module
public class ModuleXWithModule2SpecificDependencies { ... }

@Module(includes = ModuleXWithModule1SpecificDependencies.class)
public class Module1 { ... }

@Module(includes = ModuleXWithModule2SpecificDependencies.class)
public class Module2 { ... }

Yardımlı yerleştirme

Destekli yerleştirme, bazı parametrelerin DI çerçevesi tarafından sağlanabileceği, bazılarının ise oluşturma sırasında kullanıcı tarafından geçirilmesi gereken bir nesne oluşturmak için kullanılan bir DI kalıbıdır.

Android'de bu kalıp, Dagger'ın DI grafiğini oluşturduğu derleme zamanında değil, gösterilecek öğenin kimliğinin yalnızca çalışma zamanında bilindiği ayrıntılar ekranlarında yaygındır. Dagger ile destekli enjeksiyon hakkında daha fazla bilgi edinmek için Dagger belgelerine bakın.

Sonuç

Henüz yapmadıysanız en iyi uygulamalar bölümünü inceleyin. Dagger'ın Android uygulamasında nasıl kullanılacağını öğrenmek için Dagger'ı Android uygulaması codelab'inde kullanma bölümüne bakın.