Project yang memiliki beberapa modul Gradle dikenal sebagai project multi-modul.
Dalam project multi-modul yang dikirim sebagai APK tunggal tanpa modul
fitur, memiliki modul app
yang dapat bergantung pada sebagian besar
modul project Anda dan modul base
atau core
yang biasa diandalkan oleh modul
lainnya merupakan hal yang umum. Modul app
biasanya berisi class
Application
, sedangkan modul base
berisi semua class umum yang yang digunakan bersama di semua modul dalam project Anda.
Modul app
adalah tempat yang cocok untuk mendeklarasikan komponen aplikasi Anda (misalnya,
ApplicationComponent
pada gambar di bawah ini) yang dapat menyediakan objek
yang mungkin diperlukan komponen lain serta singleton aplikasi Anda. Sebagai
contoh, class seperti OkHttpClient
, parser JSON, pengakses untuk database Anda,
atau objek SharedPreferences
yang dapat ditentukan dalam modul core
,
akan disediakan oleh ApplicationComponent
yang ditentukan dalam modul app
.
Dalam modul app
, Anda juga dapat memiliki komponen lain dengan masa aktif yang lebih pendek.
Contohnya dapat berupa UserComponent
dengan konfigurasi khusus pengguna
(seperti UserSession
) setelah login.
Di berbagai modul project, Anda dapat menentukan setidaknya satu subkomponen yang memiliki logika yang khusus untuk modul tersebut seperti yang ditunjukkan dalam gambar 1.
Misalnya, dalam modul login
, Anda dapat memiliki LoginComponent
yang dicakupkan dengan anotasi @ModuleScope
kustom yang dapat menyediakan objek yang umum untuk fitur tersebut seperti LoginRepository
. Di dalam modul tersebut, Anda juga
dapat memiliki komponen lain yang bergantung pada LoginComponent
dengan cakupan kustom
yang berbeda. Misalnya, @FeatureScope
untuk LoginActivityComponent
atau
TermsAndConditionsComponent
, yang mana Anda dapat mencakup logika yang lebih mengedepankan fitur
seperti objek ViewModel
.
Untuk modul lainnya seperti Registration
, Anda akan memiliki konfigurasi yang serupa.
Aturan umum untuk project multi-modul adalah bahwa modul di tingkat yang sama tidak boleh saling bergantung satu sama lain. Namun, jika modul saling bergantung, pertimbangkan apakah logika yang digunakan bersama oleh modul (dependensi antara modul tersebut) harus menjadi bagian dari modul induk. Jika demikian, lakukan pemfaktoran ulang untuk memindahkan class ke modul induk; jika tidak, buat modul baru yang memperluas modul induk dan buat kedua modul asli memperluas modul baru.
Sebagai praktik terbaik, Anda umumnya akan membuat komponen di modul dalam kasus berikut:
Anda harus melakukan injeksi kolom, seperti pada
LoginActivityComponent
.Anda harus mencakup objek, seperti pada
LoginComponent
.
Jika kedua kasus tersebut tidak berlaku dan Anda perlu memberi tahu Dagger cara menyediakan
objek dari modul tersebut, buat dan tampilkan modul Dagger dengan metode @Provides
atau
@Binds
jika injeksi konstruksi tidak memungkinkan untuk class tersebut.
Implementasi dengan subkomponen Dagger
Halaman dokumen Menggunakan Dagger di aplikasi Android membahas cara membuat dan menggunakan
subkomponen. Namun, Anda tidak dapat menggunakan kode yang sama karena
modul fitur tidak mengetahui modul app
. Sebagai contoh, jika Anda mempertimbangkan
alur Login standar dan kode yang kami miliki di halaman sebelumnya, kode tersebut tidak
akan dikompilasi lagi:
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); ... } }
Alasannya adalah karena modul login
tidak mengetahui MyApplication
atau
appComponent
. Agar berfungsi, Anda harus menentukan antarmuka dalam modul
fitur yang menyediakan FeatureComponent
yang perlu diimplementasikan
oleh MyApplication
.
Pada contoh berikut, Anda dapat menentukan antarmuka LoginComponentProvider
yang menyediakan LoginComponent
dalam modul login
untuk alur Login:
Kotlin
interface LoginComponentProvider { fun provideLoginComponent(): LoginComponent }
Java
public interface LoginComponentProvider { public LoginComponent provideLoginComponent(); }
Sekarang, LoginActivity
akan menggunakan antarmuka tersebut, bukan cuplikan kode
yang ditentukan di atas:
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); ... } }
Sekarang, MyApplication
perlu menerapkan antarmuka tersebut dan menerapkan
metode yang diperlukan:
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(); } }
Berikut cara menggunakan subkomponen Dagger dalam project multi-modul. Dengan modul fitur, solusinya berbeda karena cara modul bergantung satu sama lain.
Dependensi komponen dengan modul fitur
Dengan modul fitur, cara umum modul bergantung
satu sama lain akan dibalik. Modul app
bukan menyertakan modul
fitur, tetapi modul fitur bergantung pada modul app
. Lihat gambar 2
untuk representasi cara penyusunan modul.
Dalam Dagger, komponen harus mengetahui subkomponennya. Informasi ini
disertakan dalam modul Dagger yang ditambahkan ke komponen induk (seperti
modul SubcomponentsModule
yang ada di halaman Menggunakan Dagger di aplikasi Android).
Sayangnya, dengan dependensi terbalik antara aplikasi dan
modul fitur, subkomponen tidak terlihat dari modul app
karena
tidak berada dalam jalur build. Sebagai contoh, LoginComponent
yang ditentukan dalam
modul fitur login
tidak boleh menjadi subkomponen dari ApplicationComponent
yang ditentukan dalam modul app
.
Dagger memiliki mekanisme yang disebut dependensi komponen yang dapat Anda gunakan untuk menyelesaikan masalah ini. Alih-alih komponen turunan menjadi subkomponen dari komponen induk, komponen turunan akan bergantung pada komponen induk. Dengan demikian, tidak ada hubungan induk-turunan; komponen kini bergantung pada komponen lain untuk mendapatkan dependensi tertentu. Komponen harus menampilkan jenis dari grafik agar komponen dependen dapat memakainya.
Misalnya: modul fitur yang disebut login
ingin membuat
LoginComponent
yang bergantung pada AppComponent
yang tersedia di
modul Gradle app
.
Berikut adalah definisi untuk class dan AppComponent
yang merupakan bagian dari
modul Gradle app
:
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 { ... }
Dalam modul gradle login
yang menyertakan modul gradle app
, Anda memiliki
LoginActivity
yang memerlukan instance LoginViewModel
untuk diinjeksi:
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
memiliki dependensi pada UserRepository
yang tersedia dan
tercakup di AppComponent
. Mari kita buat LoginComponent
yang bergantung pada
AppComponent
untuk menginjeksi LoginActivity
:
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
menentukan dependensi pada AppComponent
dengan menambahkannya
ke parameter dependensi anotasi komponen. Karena LoginActivity
akan
diinjeksi oleh Dagger, tambahkan metode inject()
ke antarmuka.
Saat membuat LoginComponent
, instance AppComponent
harus
diteruskan. Gunakan factory komponen untuk melakukannya:
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); }
Sekarang, LoginActivity
dapat membuat instance LoginComponent
dan memanggil
metode inject()
.
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
bergantung pada UserRepository
; dan agar LoginComponent
dapat
mengaksesnya dari AppComponent
, AppComponent
harus menampilkannya dalam
antarmukanya:
Kotlin
@Singleton @Component interface AppComponent { fun userRepository(): UserRepository }
Java
@Singleton @Component public interface AppComponent { UserRepository userRepository(); }
Aturan pembatasan pada komponen dependen berfungsi dengan cara yang sama
seperti pada subkomponen. Karena menggunakan instance AppComponent
,
LoginComponent
tidak dapat menggunakan anotasi cakupan yang sama.
Jika ingin LoginViewModel
tercakup di LoginComponent
, Anda akan melakukannya seperti
yang Anda lakukan sebelumnya menggunakan anotasi @ActivityScope
kustom.
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; } }
Praktik terbaik
ApplicationComponent
harus selalu berada dalam modulapp
.Buat komponen Dagger di modul jika Anda perlu melakukan injeksi kolom dalam modul tersebut atau Anda harus mencakup objek untuk alur tertentu dari aplikasi Anda.
Untuk modul Gradle yang dimaksudkan untuk menjadi utilitas atau helper dan tidak perlu membuat grafik (karena itulah Anda memerlukan komponen Dagger), buat dan tampilkan modul Dagger umum dengan metode @Provides dan @Binds class tersebut yang tidak mendukung injeksi konstruktor.
Untuk menggunakan Dagger di aplikasi Android dengan modul fitur, gunakan dependensi komponen agar dapat mengakses dependensi yang disediakan oleh
ApplicationComponent
yang ditentukan dalam modulapp
.