Çok modüllü uygulamalarda Dagger'ı kullanma

Birden fazla Gradle modülü bulunan projelere çok modüllü proje denir. Özellik modülü olmayan tek bir APK olarak gönderilen çok modüllü bir projede, projenizin çoğu modülüne bağlı olabilen bir app modülüne ve geri kalan modüllerin genellikle bağımlı olduğu base veya core modülüne sahip olmak yaygın bir durumdur. app modülü genellikle Application sınıfınızı içerirken base modülü, projenizdeki tüm modüllerde paylaşılan tüm ortak sınıfları içerir.

app modülü, uygulamanızın tekil tonlarının yanı sıra diğer bileşenlerin ihtiyaç duyabileceği nesneleri de sağlayabilen uygulama bileşeninizi (örneğin, aşağıdaki resimde ApplicationComponent) tanımlamak için uygundur. Örnek olarak OkHttpClient, JSON ayrıştırıcılar, veritabanınızın erişimcileri veya core modülünde tanımlanabilecek SharedPreferences nesneleri gibi sınıflar app modülünde tanımlanan ApplicationComponent tarafından sağlanır.

app modülünde, daha kısa ömürlü başka bileşenler de bulunabilir. Örnek olarak, giriş yaptıktan sonra kullanıcıya özel yapılandırmaya (UserSession gibi) sahip bir UserComponent verilebilir.

Projenizin farklı modüllerinde, Şekil 1'de görüldüğü gibi ilgili modüle özgü mantığa sahip en az bir alt bileşen tanımlayabilirsiniz.

Şekil 1. Çok modüllü bir projedeki Dagger grafiği örneği

Örneğin, bir login modülünde, LoginRepository gibi özelliklerle yaygın olarak kullanılan nesneleri sağlayabilen özel bir @ModuleScope ek açıklamasına sahip bir LoginComponent kapsamınız olabilir. Bu modülün içinde, farklı bir özel kapsama sahip LoginComponent öğesine bağlı başka bileşenler de bulunabilir. Örneğin, LoginActivityComponent için @FeatureScope veya ViewModel nesneleri gibi özelliğe daha çok özgü mantığın kapsamına girebileceğiniz bir TermsAndConditionsComponent.

Registration gibi diğer modüller için de benzer bir kurulumunuz olurdu.

Çok modüllü projelerde genel kural, aynı düzeydeki modüllerin birbirine bağlı olmaması gerektiğidir. Çalışıyorlarsa bu paylaşılan mantığın (aralarındaki bağımlılıklar) üst modülün bir parçası olup olmayacağını göz önünde bulundurun. Bu durumda, sınıfları üst modüle taşımak için yeniden düzenleyin. Taşıyamıyorsanız üst modülü genişleten ve orijinal modüllerin her ikisinin de yeni modülü genişletmesini sağlayan yeni bir modül oluşturun.

En iyi uygulama olarak, aşağıdaki durumlarda genellikle bir modül içinde bileşen oluşturursunuz:

  • LoginActivityComponent'te olduğu gibi alan ekleme işlemini gerçekleştirmeniz gerekir.

  • LoginComponent ile olduğu gibi nesnelerin kapsamını ayarlamanız gerekir.

Bu kumarhanelerin hiçbiri geçerli değilse ve Dagger'a bu modülden nesneleri nasıl sağlayacağını bildirmeniz gerekiyorsa, Dagger modülünü @Provides veya @Binds yöntemleriyle oluşturup açığa çıkarın (bu sınıflarda inşaat yerleştirmesi mümkün değilse).

Dagger alt bileşenleriyle uygulama

Android uygulamalarında Dagger'ı kullanma doküman sayfasında alt bileşenlerin nasıl oluşturulacağı ve kullanılacağı ele alınmaktadır. Ancak özellik modülleri app modülü hakkında bilgi sahibi olmadığı için aynı kodu kullanamazsınız. Örneğin, tipik bir Giriş akışını ve önceki sayfada bulunan kodu düşündüğünüzde artık derlenmez:

Kotlin

class LoginActivity: Activity() {
  ...

  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)
    ...
  }
}

Java

public class LoginActivity extends Activity {
    ...

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

        ...
    }
}

Bunun nedeni, login modülünün MyApplication ve appComponent hakkında bilgi sahibi olmamasıdır. Bunu yapmak için özellik modülünde MyApplication uygulamasının uygulaması gereken FeatureComponent sağlayan bir arayüz tanımlamanız gerekir.

Aşağıdaki örnekte, login modülünde Giriş akışı için LoginComponent sağlayan bir LoginComponentProvider arayüzü tanımlayabilirsiniz:

Kotlin

interface LoginComponentProvider {
    fun provideLoginComponent(): LoginComponent
}

Java

public interface LoginComponentProvider {
   public LoginComponent provideLoginComponent();
}

Artık LoginActivity, yukarıda tanımlanan kod snippet'i yerine bu arayüzü kullanacaktır:

Kotlin

class LoginActivity: Activity() {
  ...

  override fun onCreate(savedInstanceState: Bundle?) {
    loginComponent = (applicationContext as LoginComponentProvider)
                        .provideLoginComponent()

    loginComponent.inject(this)
    ...
  }
}

Java

public class LoginActivity extends Activity {
    ...

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        loginComponent = ((LoginComponentProvider) getApplicationContext())
                                .provideLoginComponent();

        loginComponent.inject(this);

        ...
    }
}

Şimdi MyApplication ürününün bu arayüzü ve gerekli yöntemleri uygulaması gerekiyor:

Kotlin

class MyApplication: Application(), LoginComponentProvider {
  // Reference to the application graph that is used across the whole app
  val appComponent = DaggerApplicationComponent.create()

  override fun provideLoginComponent(): LoginComponent {
    return appComponent.loginComponent().create()
  }
}

Java

public class MyApplication extends Application implements LoginComponentProvider {
  // Reference to the application graph that is used across the whole app
  ApplicationComponent appComponent = DaggerApplicationComponent.create();

  @Override
  public LoginComponent provideLoginComponent() {
    return appComponent.loginComponent.create();
  }
}

Çok modüllü bir projede Dagger alt bileşenlerini bu şekilde kullanabilirsiniz. Özellik modülleriyle ilgili çözüm, modüllerin birbirine bağlılığı nedeniyle farklıdır.

Özellik modülleriyle bileşen bağımlılıkları

Özellik modüllerinde, modüllerin genellikle birbirine bağlı olma şekli tersine çevrilir. Özellik modüllerini içeren app modülü yerine, özellik modülleri app modülüne bağlıdır. Modüllerin nasıl yapılandırıldığını görmek için Şekil 2'ye bakın.

2. Şekil. Özellik modülleri bulunan bir projedeki Dagger grafiği örneği

Dagger'da, bileşenlerin alt bileşenleri hakkında bilgi sahibi olması gerekir. Bu bilgiler, üst bileşene eklenen bir Dagger modülünde yer alır (Android uygulamalarında Dagger'ı kullanma bölümündeki SubcomponentsModule modülü gibi).

Maalesef uygulama ile özellik modülü arasındaki tersine bağımlılık nedeniyle alt bileşen, derleme yolunda olmadığı için app modülünden görünmez. Örneğin, bir login özellik modülünde tanımlanan LoginComponent, app modülünde tanımlanan ApplicationComponent öğesinin alt bileşeni olamaz.

Dagger'ın bu sorunu çözmek için kullanabileceğiniz, bileşen bağımlılıkları adlı bir mekanizması vardır. Alt bileşen, üst bileşenin alt bileşeni yerine üst bileşene bağımlıdır. Bununla birlikte, üst-alt ilişkisi yoktur. Artık bileşenler belirli bağımlılıkları elde etmek için diğerlerine bağımlıdır. Bağımlı bileşenlerin tüketmesi için bileşenlerin grafikten türleri göstermesi gerekir.

Örneğin, login adlı bir özellik modülü, Gradle modülünde bulunan AppComponent öğesine dayalı bir LoginComponent oluşturmak istiyor.app

Aşağıda, app Gradle modülünün parçası olan sınıf ve AppComponent ile ilgili tanımlar verilmiştir:

Kotlin

// UserRepository's dependencies
class UserLocalDataSource @Inject constructor() { ... }
class UserRemoteDataSource @Inject constructor() { ... }

// UserRepository is scoped to AppComponent
@Singleton
class UserRepository @Inject constructor(
    private val localDataSource: UserLocalDataSource,
    private val remoteDataSource: UserRemoteDataSource
) { ... }

@Singleton
@Component
interface AppComponent { ... }

Java

// UserRepository's dependencies
public class UserLocalDataSource {

    @Inject
    public UserLocalDataSource() {}
}

public class UserRemoteDataSource {

    @Inject
    public UserRemoteDataSource() { }
}

// UserRepository is scoped to AppComponent
@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;
    }
}

@Singleton
@Component
public interface ApplicationComponent { ... }

app gradle modülünü içeren login gradle modülünüzde LoginViewModel örneğinin eklenmesi gereken bir LoginActivity var:

Kotlin

// LoginViewModel depends on UserRepository that is scoped to AppComponent
class LoginViewModel @Inject constructor(
    private val userRepository: UserRepository
) { ... }

Java

// LoginViewModel depends on UserRepository that is scoped to AppComponent
public class LoginViewModel {

    private final UserRepository userRepository;

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

LoginViewModel, kullanılabilir ve kapsamı AppComponent kapsamındaki UserRepository için bir bağımlılığa sahip. LoginActivity öğesini eklemek için AppComponent öğesine bağlı bir LoginComponent oluşturalım:

Kotlin

// Use the dependencies attribute in the Component annotation to specify the
// dependencies of this Component
@Component(dependencies = [AppComponent::class])
interface LoginComponent {
    fun inject(activity: LoginActivity)
}

Java

// Use the dependencies attribute in the Component annotation to specify the
// dependencies of this Component
@Component(dependencies = AppComponent.class)
public interface LoginComponent {

    void inject(LoginActivity loginActivity);
}

LoginComponent, AppComponent için bağımlılığı bileşen ek açıklamasının bağımlılık parametresine ekleyerek belirtir. LoginActivity, Dagger tarafından yerleştirileceği için arayüze inject() yöntemini ekleyin.

LoginComponent oluşturulurken AppComponent örneğinin aktarılması gerekir. Bunu yapmak için bileşeni fabrika ayarlarına kullanın:

Kotlin

@Component(dependencies = [AppComponent::class])
interface LoginComponent {

    @Component.Factory
    interface Factory {
        // Takes an instance of AppComponent when creating
        // an instance of LoginComponent
        fun create(appComponent: AppComponent): LoginComponent
    }

    fun inject(activity: LoginActivity)
}

Java

@Component(dependencies = AppComponent.class)
public interface LoginComponent {

    @Component.Factory
    interface Factory {
        // Takes an instance of AppComponent when creating
        // an instance of LoginComponent
        LoginComponent create(AppComponent appComponent);
    }

    void inject(LoginActivity loginActivity);
}

Artık LoginActivity, LoginComponent örneği oluşturabilir ve inject() yöntemini çağırabilir.

Kotlin

class LoginActivity: Activity() {

    // You want Dagger to provide an instance of LoginViewModel from the Login graph
    @Inject lateinit var loginViewModel: LoginViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        // Gets appComponent from MyApplication available in the base Gradle module
        val appComponent = (applicationContext as MyApplication).appComponent

        // Creates a new instance of LoginComponent
        // Injects the component to populate the @Inject fields
        DaggerLoginComponent.factory().create(appComponent).inject(this)

        super.onCreate(savedInstanceState)

        // Now you can access loginViewModel
    }
}

Java

public class LoginActivity extends Activity {

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

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Gets appComponent from MyApplication available in the base Gradle module
        AppComponent appComponent = ((MyApplication) getApplicationContext()).appComponent;

        // Creates a new instance of LoginComponent
        // Injects the component to populate the @Inject fields
        DaggerLoginComponent.factory().create(appComponent).inject(this);

        // Now you can access loginViewModel
    }
}

LoginViewModel, UserRepository ürününe bağlıdır. LoginComponent ürününün AppComponent ürününden erişebilmesi için AppComponent, bu öğeyi arayüzünde görüntülemelidir:

Kotlin

@Singleton
@Component
interface AppComponent {
    fun userRepository(): UserRepository
}

Java

@Singleton
@Component
public interface AppComponent {
    UserRepository userRepository();
}

Bağımlı bileşenlere sahip kapsam oluşturma kuralları, alt bileşenlerle aynı şekilde çalışır. LoginComponent, AppComponent örneğini kullandığından aynı kapsam ek açıklamasını kullanamaz.

LoginViewModel kapsamını LoginComponent olarak ayarlamak isterseniz daha önce özel @ActivityScope ek açıklamasını kullanarak bunu yapmanız gerekir.

Kotlin

@ActivityScope
@Component(dependencies = [AppComponent::class])
interface LoginComponent { ... }

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

Java

@ActivityScope
@Component(dependencies = AppComponent.class)
public interface LoginComponent { ... }

@ActivityScope
public class LoginViewModel {

    private final UserRepository userRepository;

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

En iyi uygulamalar

  • ApplicationComponent daima app modülünde olmalıdır.

  • Söz konusu modülde alan yerleştirme yapmanız veya nesnelerin kapsamını uygulamanızın belirli bir akışı için kapsamanız gerekiyorsa modüllerde Dagger bileşenleri oluşturun.

  • Yardımcı programlar veya yardımcılar olması amaçlanan ve grafik oluşturmanız gerekmeyen Gradle modülleri için (Bu nedenle Dagger bileşeni gerekir) genel Dagger modüllerini, oluşturucu yerleştirmeyi desteklemeyen bu sınıfların @Provides ve @Binds yöntemlerini kullanarak oluşturun ve kullanıma sunun.

  • Dagger'ı özellik modülleri içeren bir Android uygulamasında kullanmak için, app modülünde tanımlanan ApplicationComponent tarafından sağlanan bağımlılıklara erişebilmek amacıyla bileşen bağımlılıklarını kullanın.