Halaman Dasar-dasar Dagger menjelaskan cara Dagger dapat membantu Anda mengotomatiskan injeksi dependensi dalam aplikasi Anda. Dengan Dagger, Anda tidak perlu menuliskan kode boilerplate yang sangat panjang dan rawan error.
Ringkasan praktik terbaik
- Gunakan injeksi konstruktor dengan
@Inject
untuk menambahkan jenis ke grafik Dagger jika memungkinkan. Jika tidak:- Gunakan
@Binds
untuk memberi tahu Dagger tentang implementasi yang harus dimiliki antarmuka. - Gunakan
@Provides
untuk memberi tahu Dagger cara menyediakan class yang tidak dimiliki project Anda.
- Gunakan
- Anda hanya boleh mendeklarasikan modul satu kali dalam sebuah komponen.
- Beri nama anotasi cakupan sesuai dengan masa aktif saat
anotasi digunakan. Contohnya meliputi
@ApplicationScope
,@LoggedUserScope
, dan@ActivityScope
.
Menambahkan dependensi
Untuk menggunakan Dagger dalam project, tambahkan dependensi ini pada aplikasi Anda di
file build.gradle
. Anda dapat menemukan versi terbaru Dagger
dalam project GitHub ini.
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' }
Dagger di Android
Pertimbangkan contoh aplikasi Android dengan grafik dependensi dari Gambar 1.
Di Android, Anda biasanya membuat grafik Dagger yang ada dalam class
aplikasi karena Anda ingin instance dari grafik berada dalam memori selama
aplikasi berjalan. Dengan cara ini, grafik disertakan ke siklus proses aplikasi. Dalam beberapa
kasus, Anda mungkin juga ingin memiliki konteks aplikasi yang tersedia dalam
grafik. Untuk itu, Anda juga perlu menempatkan grafik dalam class
Application
. Satu keuntungan dari
pendekatan ini adalah bahwa grafik tersedia untuk class framework Android lainnya.
Selain itu, pendekatan ini menyederhanakan pengujian dengan mengizinkan Anda menggunakan
class Application
kustom dalam pengujian.
Karena antarmuka yang menghasilkan grafik dianotasi dengan @Component
,
Anda dapat menyebutnya ApplicationComponent
atau ApplicationGraph
. Anda biasanya menyimpan
instance komponen tersebut dalam class Application
kustom dan memanggilnya setiap
kali membutuhkan grafik aplikasi, seperti yang ditampilkan dalam cuplikan
kode berikut:
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(); }
Karena class framework Android tertentu, seperti aktivitas dan fragmen,
dibuat instance-nya oleh sistem, Dagger tidak dapat membuatnya untuk Anda. Khusus untuk
aktivitas, setiap kode inisialisasi harus dimasukkan ke metode onCreate()
.
Artinya, Anda tidak dapat menggunakan anotasi @Inject
dalam konstruktor
class (injeksi konstruktor) seperti yang Anda lakukan dalam contoh sebelumnya. Sebagai gantinya,
Anda harus menggunakan injeksi kolom.
Anda ingin agar Dagger mengisi dependensi tersebut untuk Anda, bukan membuat sendiri dependensi yang diperlukan aktivitas dalam metode
onCreate()
. Untuk injeksi
kolom, Anda dapat menerapkan anotasi @Inject
ke kolom yang ingin Anda
dapatkan dari grafik Dagger.
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; }
Untuk mempermudah, LoginViewModel
bukanlah ViewModel
Komponen Arsitektur Android. Ini hanya class reguler yang berfungsi sebagai ViewModel.
Untuk informasi selengkapnya tentang cara menginjeksikan class ini, lihat kode
dalam Implementasi Dagger Cetak Biru Android yang resmi, di
cabang dev-dagger.
Salah satu pertimbangan dengan Dagger adalah kolom yang diinjeksikan tidak dapat bersifat pribadi. Kolom tersebut harus memiliki setidaknya visibilitas paket pribadi seperti dalam kode sebelumnya.
Memasukkan aktivitas
Dagger perlu memahami bahwa LoginActivity
harus mengakses grafik agar dapat
menyediakan ViewModel
yang dibutuhkan. Dalam halaman Dasar-dasar Dagger Anda menggunakan
antarmuka @Component
untuk mendapatkan objek dari grafik
dengan cara memaparkan fungsi dengan jenis nilai yang ditampilkan dari sesuatu yang ingin Anda dapatkan dari
grafik tersebut. Dalam hal ini, Anda perlu memberi tahu Dagger tentang objek (LoginActivity
dalam kasus ini) yang memerlukan dependensi untuk diinjeksikan. Untuk itu, Anda mengekspos
fungsi yang menggunakan objek yang meminta injeksi sebagai parameter.
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); }
Fungsi ini memberi tahu Dagger bahwa LoginActivity
ingin mengakses grafik dan
meminta injeksi. Dagger harus memenuhi semua dependensi yang
diperlukan LoginActivity
(LoginViewModel
dengan dependensinya sendiri).
Jika Anda memiliki beberapa class yang meminta injeksi, Anda harus secara khusus
mendeklarasikan semuanya dalam komponen dengan jenis persisnya. Misalnya, jika Anda memiliki
LoginActivity
dan RegistrationActivity
yang meminta injeksi, Anda akan memiliki dua
metode inject()
, bukan metode generik yang mencakup kedua kasus tersebut. Metode
inject()
generik tidak memberi tahu Dagger apa yang perlu disediakan. Fungsi
dalam antarmuka dapat bernama apa saja, tetapi memanggilnya inject()
saat
menerima objek untuk diinjeksikan sebagai parameter merupakan suatu konvensi dalam Dagger.
Untuk memasukkan objek dalam aktivitas, Anda perlu menggunakan appComponent
yang ditetapkan dalam
class Application
Anda dan memanggil metode inject()
, meneruskan instance
aktivitas yang meminta injeksi.
Saat menggunakan aktivitas, injeksikan Dagger dalam
metode onCreate()
aktivitas sebelum memanggil super.onCreate()
untuk menghindari masalah
pemulihan fragmen. Selama fase pemulihan dalam super.onCreate()
,
aktivitas akan menyertakan fragmen yang mungkin ingin mengakses binding aktivitas.
Saat menggunakan fragmen, injeksikan Dagger dalam metode onAttach()
fragmen. Dalam hal ini, hal tersebut dapat dilakukan sebelum atau setelah memanggil super.onAttach()
.
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; } }
Beri tahu Dagger tentang cara menyediakan sisa dependensi untuk membuat grafik:
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; } }
Modul dagger
Untuk contoh ini, Anda menggunakan library jaringan Retrofit.
UserRemoteDataSource
memiliki dependensi di LoginRetrofitService
. Akan tetapi,
cara untuk membuat instance LoginRetrofitService
berbeda dengan yang
Anda lakukan hingga saat ini. Ini bukan pembuatan instance class, tetapi merupakan hasil
memanggil Retrofit.Builder()
dan meneruskan parameter yang berbeda untuk mengonfigurasi
layanan login.
Selain anotasi @Inject
, ada cara lain untuk memberi tahu Dagger tentang cara
menyediakan instance suatu class: informasi di dalam modul Dagger. Modul
Dagger adalah class yang dianotasi dengan @Module
. Di sana, Anda dapat menentukan
dependensi dengan anotasi @Provides
.
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); } }
Modul adalah cara untuk merangkum informasi tentang cara menyediakan
objek secara semantik. Seperti yang Anda lihat, Anda memanggil class NetworkModule
untuk mengelompokkan logika
penyediaan objek yang berkaitan dengan jaringan. Jika aplikasi diperluas, Anda juga dapat
menambahkan cara untuk menyediakan OkHttpClient
di sini, atau cara
untuk mengonfigurasi Gson atau Moshi.
Dependensi metode @Provides
adalah parameter metode tersebut. Untuk
metode sebelumnya, LoginRetrofitService
dapat disediakan tanpa dependensi
karena metode tersebut tidak memiliki parameter. Jika Anda telah mendeklarasikan OkHttpClient
sebagai
suatu parameter, Dagger harus menyediakan instance OkHttpClient
dari
grafik untuk memenuhi dependensi LoginRetrofitService
. Contoh:
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) { ... } }
Agar grafik Dagger mengetahui tentang modul ini, Anda harus menambahkannya ke
antarmuka @Component
sebagai berikut:
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 { ... }
Cara yang disarankan untuk menambahkan jenis ke grafik Dagger adalah dengan menggunakan injeksi
konstruktor (misalnya dengan anotasi @Inject
pada konstruktor class).
Terkadang hal ini tidak mungkin dilakukan sehingga Anda harus menggunakan modul Dagger. Salah satu contohnya
adalah saat Anda ingin agar Dagger menggunakan hasil komputasi untuk menentukan cara
membuat instance sebuah objek. Setiap kali harus menyediakan instance dari jenis
tersebut, Dagger menjalankan kode di dalam metode @Provides
.
Berikut adalah tampilan grafik Dagger dalam contoh saat ini:
Titik entri ke grafik tersebut adalah LoginActivity
. Karena LoginActivity
menginjeksikan
LoginViewModel
, Dagger membangun grafik yang mengetahui cara menyediakan instance
LoginViewModel
, dan secara rekursif, dari dependensinya. Dagger mengetahui cara
melakukannya karena anotasi @Inject
pada konstruktor class.
Di dalam ApplicationComponent
yang dibuat oleh Dagger, ada metode
jenis factory untuk mendapatkan instance dari semua class yang diketahui cara penyediaannya. Dalam
contoh ini, Dagger mendelegasikan ke NetworkModule
yang disertakan dalam
ApplicationComponent
untuk mendapatkan instance LoginRetrofitService
.
Cakupan dagger
Cakupan disebutkan pada halaman Dasar-dasar Dagger sebagai cara agar memiliki instance unik dari suatu jenis dalam komponen. Inilah yang dimaksud dengan mencakupkan jenis pada siklus proses komponen.
Karena Anda mungkin ingin menggunakan UserRepository
dalam fitur lain aplikasi dan
mungkin tidak ingin membuat objek baru setiap kali membutuhkannya, Anda dapat menetapkannya
sebagai instance unik untuk seluruh aplikasi. Sama halnya dengan
LoginRetrofitService
. Pembuatannya mungkin akan mahal, dan sebaiknya
instance unik objek tersebut dapat digunakan kembali. Membuat instance
UserRemoteDataSource
tidak semahal itu, sehingga pencakupannya pada
siklus proses komponen tidak diperlukan.
@Singleton
adalah satu-satunya anotasi cakupan yang disertakan
dengan paket javax.inject
. Anda dapat menggunakannya untuk menganotasi ApplicationComponent
dan objek yang ingin Anda gunakan kembali di seluruh aplikasi.
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() { ... } }
Hati-hati agar tidak menimbulkan kebocoran memori saat menerapkan cakupan ke objek. Selama
komponen cakupan ada dalam memori, objek yang dibuat juga ada dalam
memori. Karena ApplicationComponent
dibuat saat aplikasi diluncurkan (dalam
class Application
), objek juga akan dihancurkan ketika aplikasi dihancurkan. Jadi,
instance unik UserRepository
selalu ada di memori hingga
aplikasi dihancurkan.
Subkomponen Dagger
Jika alur login Anda (yang dikelola oleh satu LoginActivity
) terdiri dari beberapa
fragmen, Anda harus menggunakan kembali instance LoginViewModel
yang sama di semua
fragmen. @Singleton
tidak dapat menganotasi LoginViewModel
untuk menggunakan kembali instance
karena alasan berikut:
Instance
LoginViewModel
akan tetap ada dalam memori setelah alur selesai.Sebaiknya Anda menggunakan instance yang berbeda dari
LoginViewModel
untuk setiap alur login. Misalnya, jika pengguna logout, sebaiknya gunakan instanceLoginViewModel
yang berbeda, bukan instance yang sama seperti saat pengguna login untuk pertama kalinya.
Untuk mencakupkan LoginViewModel
ke siklus proses LoginActivity
, Anda harus membuat
komponen baru (subgrafik baru) untuk alur login dan cakupan yang baru.
Mari kita buat grafik khusus untuk alur login.
Kotlin
@Component interface LoginComponent {}
Java
@Component public interface LoginComponent { }
Sekarang, LoginActivity
harus mendapatkan injeksi dari LoginComponent
karena
memiliki konfigurasi khusus login. Tindakan ini akan menghapus tanggung jawab untuk menginjeksikan LoginActivity
dari class ApplicationComponent
.
Kotlin
@Component interface LoginComponent { fun inject(activity: LoginActivity) }
Java
@Component public interface LoginComponent { void inject(LoginActivity loginActivity); }
LoginComponent
harus dapat mengakses objek dari ApplicationComponent
karena LoginViewModel
bergantung pada UserRepository
. Cara memberi tahu Dagger bahwa
Anda ingin komponen baru menggunakan bagian komponen lain adalah dengan
subkomponen Dagger. Komponen baru harus merupakan subkomponen dari
komponen yang berisi resource bersama.
Subkomponen adalah komponen yang mewarisi dan memperluas grafik objek dari komponen induk. Jadi, semua objek yang disediakan di komponen induk juga disediakan dalam subkomponen. Dengan cara ini, objek dari subkomponen dapat bergantung pada objek yang disediakan oleh komponen induk.
Untuk membuat instance subkomponen, Anda memerlukan instance dari komponen induk. Oleh karena itu, objek yang disediakan oleh komponen induk ke subkomponen masih tercakup pada komponen induk.
Dalam contoh ini, Anda harus menentukan LoginComponent
sebagai subkomponen dari
ApplicationComponent
. Untuk melakukannya, anotasikan LoginComponent
dengan
@Subcomponent
:
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); }
Anda juga harus menentukan factory subkomponen di dalam LoginComponent
sehingga
ApplicationComponent
mengetahui cara membuat instance LoginComponent
.
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); }
Untuk memberi tahu Dagger bahwa LoginComponent
adalah subkomponen
ApplicationComponent
, Anda harus menunjukkannya dengan:
Membuat modul Dagger baru (misalnya
SubcomponentsModule
) yang meneruskan class subkomponen ke atributsubcomponents
anotasi.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 { }
Menambahkan modul baru (misalnya
SubcomponentsModule
) keApplicationComponent
: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 { }
Perhatikan bahwa
ApplicationComponent
tidak perlu menginjekikanLoginActivity
lagi karena tanggung jawab tersebut sekarang milikLoginComponent
, jadi Anda dapat menghapus metodeinject()
dariApplicationComponent
.Konsumen
ApplicationComponent
perlu mengetahui cara membuat instanceLoginComponent
. Komponen induk harus menambahkan metode dalam antarmukanya untuk memungkinkan konsumen membuat instance subkomponen dari instance komponen induk:Ekspos factory yang membuat instance
LoginComponent
dalam antarmuka: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(); }
Menetapkan cakupan ke subkomponen
Jika Anda membuat project, Anda dapat membuat instance dari ApplicationComponent
dan LoginComponent
. ApplicationComponent
disertakan ke siklus proses
aplikasi karena Anda ingin menggunakan instance grafik yang sama selama
aplikasi tersebut berada dalam memori.
Apa yang dimaksud dengan siklus proses LoginComponent
? Salah satu alasan mengapa Anda membutuhkan
LoginComponent
adalah karena Anda harus berbagi instance
LoginViewModel
yang sama antar-fragmen yang berkaitan dengan Login. Namun, sebaiknya gunakan instance
LoginViewModel
yang berbeda setiap kali ada alur login baru. LoginActivity
adalah masa aktif yang tepat untuk LoginComponent
: untuk setiap aktivitas baru, Anda memerlukan
instance LoginComponent
baru dan fragmen yang dapat menggunakan instance
LoginComponent
tersebut.
Karena LoginComponent
disertakan pada siklus proses LoginActivity
, Anda harus
menyimpan referensi ke komponen dalam aktivitas tersebut dengan cara yang sama seperti Anda menyimpan
referensi ke applicationComponent
dalam class Application
. Dengan demikian,
fragmen dapat mengaksesnya.
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; ... }
Perhatikan bahwa variabel loginComponent
tidak dianotasi dengan @Inject
karena Anda tidak mengharapkan variabel tersebut disediakan oleh Dagger.
Anda dapat menggunakan ApplicationComponent
untuk mendapatkan referensi ke LoginComponent
,
lalu menginjeksikan LoginActivity
seperti berikut ini:
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
dibuat dalam metode onCreate()
aktivitas, dan akan
secara implisit dimusnahkan saat aktivitas dihapus.
LoginComponent
harus selalu menyediakan instance LoginViewModel
yang sama setiap kali diminta. Anda dapat memastikan hal ini dengan membuat cakupan
anotasi kustom dan menganotasi LoginComponent
dan LoginViewModel
dengannya. Perlu
diperhatikan bahwa Anda tidak dapat menggunakan anotasi @Singleton
karena sudah digunakan
oleh komponen induk dan tindakan tersebut akan membuat objek menjadi singleton aplikasi
(instance unik untuk seluruh aplikasi). Anda perlu membuat cakupan
anotasi yang berbeda.
Dalam hal ini, Anda dapat menyebut cakupan ini @LoginScope
, tetapi ini bukan hal
yang disarankan. Nama anotasi cakupan tidak boleh secara eksplisit menyatakan tujuan
yang harus dipenuhinya. Sebaliknya, anotasi tersebut harus diberi nama sesuai masa aktifnya karena
anotasi dapat digunakan kembali oleh komponen yang setara seperti RegistrationComponent
dan SettingsComponent
. Itu sebabnya Anda harus menyebutnya @ActivityScope
, bukan
@LoginScope
.
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; } }
Sekarang, jika Anda memiliki dua fragmen yang memerlukan LoginViewModel
, keduanya
disediakan dengan instance yang sama. Misalnya, jika Anda memiliki
LoginUsernameFragment
dan LoginPasswordFragment
, keduanya harus diinjeksikan
oleh LoginComponent
:
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); }
Komponen ini mengakses instance komponen yang berada dalam
objek LoginActivity
. Contoh kode untuk LoginUserNameFragment
muncul dalam
cuplikan kode berikut:
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) } }
Dan hal yang sama berlaku untuk LoginPasswordFragment
:
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) } }
Gambar 3 menunjukkan bagaimana grafik Dagger terlihat dengan subkomponen baru. Class
dengan titik putih (UserRepository
, LoginRetrofitService
, dan LoginViewModel
)
adalah class yang memiliki instance unik dan dicakupkan pada masing-masing komponennya.
Mari kita uraikan bagian grafik tersebut:
NetworkModule
(sehinggaLoginRetrofitService
juga) disertakan dalamApplicationComponent
karena Anda menentukannya dalam komponen.UserRepository
tetap berada diApplicationComponent
karena dicakupkan keApplicationComponent
. Jika project berkembang, Anda ingin berbagi instance yang sama di berbagai fitur (misalnya Pendaftaran).Karena
UserRepository
adalah bagian dariApplicationComponent
, dependensinya (yaituUserLocalDataSource
danUserRemoteDataSource
) juga harus ada dalam komponen ini agar dapat menyediakan instanceUserRepository
.LoginViewModel
disertakan dalamLoginComponent
karena hanya diwajibkan oleh class yang diinjeksikan olehLoginComponent
.LoginViewModel
tidak disertakan dalamApplicationComponent
karena tidak ada dependensi dalamApplicationComponent
yang membutuhkanLoginViewModel
.Demikian pula, jika Anda belum mencakupkan
UserRepository
keApplicationComponent
, Dagger akan secara otomatis menyertakanUserRepository
dan dependensinya sebagai bagian dariLoginComponent
karena itulah satu-satunya tempatUserRepository
digunakan.
Selain mencakupkan objek ke siklus proses yang berbeda, membuat subkomponen adalah praktik yang baik untuk mengenkapsulasi berbagai bagian aplikasi Anda dari satu sama lain.
Membuat struktur aplikasi untuk menciptakan subgrafik Dagger yang berbeda sesuai dengan alur aplikasi Anda dapat membantu aplikasi agar menunjukkan performa yang lebih baik dan terukur dalam hal memori dan waktu startup.
Praktik terbaik saat membuat grafik Dagger
Saat membuat grafik Dagger untuk aplikasi Anda:
Saat membuat komponen, Anda harus mempertimbangkan elemen apa yang bertanggung jawab terhadap masa aktif komponen tersebut. Dalam hal ini, class
Application
bertanggung jawab atasApplicationComponent
danLoginActivity
bertanggung jawab atasLoginComponent
.Gunakan cakupan hanya jika memungkinkan. Penggunaan cakupan secara berlebihan dapat berdampak negatif pada performa runtime aplikasi Anda: objek berada dalam memori selama komponen berada dalam memori, dan mendapatkan objek cakupan akan menjadi lebih mahal. Ketika Dagger menyediakan objek, objek tersebut akan menggunakan penguncian
DoubleCheck
, bukan penyedia jenis factory.
Menguji project yang menggunakan Dagger
Salah satu manfaat menggunakan framework injeksi dependensi seperti Dagger adalah karena Dagger menjadikan pengujian kode lebih mudah.
Pengujian unit
Anda tidak perlu menggunakan Dagger untuk pengujian unit. Saat menguji class yang menggunakan injeksi konstruktor, Anda tidak perlu menggunakan Dagger untuk membuat instance class tersebut. Anda dapat langsung memanggil konstruktor yang meneruskan dependensi tiruan atau palsu secara langsung seperti yang Anda lakukan jika dependensi itu tidak dianotasi.
Misalnya, saat menguji LoginViewModel
:
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(...); } }
Pengujian menyeluruh
Untuk pengujian integrasi, praktik yang baik adalah membuat
TestApplicationComponent
yang dimaksudkan untuk pengujian.
Produksi dan pengujian menggunakan konfigurasi komponen yang berbeda.
Hal ini memerlukan desain modul yang lebih canggih dalam aplikasi Anda. Komponen pengujian memperluas komponen produksi dan menginstal serangkaian modul yang berbeda.
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
memiliki implementasi palsu atas NetworkModule
yang asli.
Di sana, Anda dapat memberikan contoh palsu atau tiruan dari apa pun yang ingin Anda ganti.
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(); } }
Dalam integrasi atau pengujian menyeluruh, Anda akan menggunakan TestApplication
yang
membuat TestApplicationComponent
, bukan ApplicationComponent
.
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(); }
Kemudian, aplikasi pengujian ini digunakan dalam TestRunner
kustom yang akan Anda gunakan untuk
menjalankan pengujian instrumentasi. Untuk informasi selengkapnya tentang hal ini, lihat Menggunakan
Dagger di codelab aplikasi Android Anda.
Bekerja dengan modul Dagger
Modul Dagger adalah cara untuk mengenkapsulasi cara menyediakan objek secara semantik. Anda dapat menyertakan modul dalam komponen, tetapi Anda juga dapat menyertakan modul di dalam modul lainnya. Metode ini sangat efektif, tetapi dapat dengan mudah disalahgunakan.
Setelah modul ditambahkan ke komponen atau modul lain, modul tersebut sudah ada di grafik Dagger. Dagger dapat menyediakan objek tersebut dalam komponen itu. Sebelum menambahkan modul, periksa apakah modul tersebut sudah menjadi bagian dari grafik Dagger dengan memeriksa apakah modul telah ditambahkan ke komponen atau dengan mengompilasi project dan melihat apakah Dagger dapat menemukan dependensi yang diperlukan untuk modul tersebut.
Praktik yang baik menentukan bahwa modul hanya boleh dideklarasikan satu kali dalam suatu komponen (di luar kasus penggunaan Dagger tingkat lanjut yang spesifik).
Misalkan grafik Anda dikonfigurasi dengan cara ini. ApplicationComponent
mencakup Module1
dan Module2
, sementara Module1
mencakup ModuleX
.
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 { ... }
Jika sekarang Module2
bergantung pada class yang disediakan oleh ModuleX
. Praktik yang buruk
termasuk ModuleX
dalam Module2
karena ModuleX
disertakan dua kali dalam
grafik seperti yang terlihat pada cuplikan kode berikut:
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 { ... }
Sebagai gantinya, Anda harus melakukan salah satu hal berikut:
- Faktorkan ulang modul dan ekstrak modul umum ke komponen.
- Buat modul baru dengan objek yang digunakan bersama oleh kedua modul, lalu ekstrak ke komponen.
Tidak melakukan pemfaktoran ulang seperti ini akan menyebabkan banyak modul, termasuk satu sama lain, tidak memiliki organisasi yang jelas dan membuatnya sulit untuk dilihat dari mana asal dependensi masing-masing.
Praktik yang baik (Opsi 1): ModulX dideklarasikan sekali dalam grafik Dagger.
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 { ... }
Praktik yang baik (Opsi 2): Dependensi umum dari Module1
dan Module2
dalam ModuleX
diekstrak menjadi modul baru bernama ModuleXCommon
yang
disertakan dalam komponen. Kemudian, dua modul lain bernama
ModuleXWithModule1Dependencies
dan ModuleXWithModule2Dependencies
dibuat dengan dependensi yang spesifik untuk masing-masing modul. Semua modul
dideklarasikan satu kali dalam grafik Dagger.
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 { ... }
Injeksi berbantuan
Injeksi berbantuan adalah pola DI yang digunakan untuk membuat objek. Dengan pola ini, beberapa parameter dapat disediakan oleh framework DI sedangkan parameter lainnya harus diteruskan saat pembuatan oleh pengguna.
Di Android, pola ini umum di layar detail tempat id elemen yang ditampilkan hanya diketahui saat runtime, bukan pada waktu kompilasi saat Dagger menghasilkan grafik DI. Untuk mempelajari lebih lanjut injeksi berbantuan dengan Dagger, lihat dokumentasi Dagger.
Kesimpulan
Jika Anda belum melakukannya, baca bagian praktik terbaik. Untuk melihat cara menggunakan Dagger di aplikasi Android, baca bagian Menggunakan Dagger di codelab aplikasi Android.