Hilt adalah library injeksi dependensi untuk Android yang mengurangi boilerplate ketika melakukan injeksi dependensi manual dalam project Anda. Dengan melakukan injeksi dependensi manual, Anda harus membuat setiap class dan dependensinya secara manual, serta menggunakan container untuk menggunakan kembali dan mengelola dependensi.
Hilt menyediakan cara standar untuk menggunakan DI dalam aplikasi Anda dengan menyediakan container untuk setiap class Android dalam project Anda dan mengelola siklus prosesnya secara otomatis. Hilt ditambahkan pada library DI yang populer Dagger untuk mendapatkan manfaat dari ketepatan waktu kompilasi, performa runtime, skalabilitas, dan dukungan Android Studio yang disediakan oleh Dagger. Untuk informasi lebih lanjut, lihat Hilt dan Dagger.
Panduan ini menjelaskan konsep dasar Hilt dan container yang dihasilkannya. Panduan ini juga mencakup demo cara melakukan bootstrap pada aplikasi yang sudah ada untuk menggunakan Hilt.
Menambahkan dependensi
Pertama, tambahkan plugin hilt-android-gradle-plugin ke file build.gradle
root project Anda:
Kotlin
plugins { ... id("com.google.dagger.hilt.android") version "2.57.1" apply false }
Groovy
plugins { ... id 'com.google.dagger.hilt.android' version '2.57.1' apply false }
Kemudian, terapkan plugin Gradle dan tambahkan dependensi ini di file
app/build.gradle Anda:
Kotlin
plugins { id("com.google.devtools.ksp") id("com.google.dagger.hilt.android") } android { ... } dependencies { implementation("com.google.dagger:hilt-android:2.57.1") ksp("com.google.dagger:hilt-android-compiler:2.57.1") }
Groovy
... plugins { id 'com.google.devtools.ksp' id 'com.google.dagger.hilt.android' } android { ... } dependencies { implementation "com.google.dagger:hilt-android:2.57.1" ksp "com.google.dagger:hilt-compiler:2.57.1" }
Untuk memastikan project Anda dikonfigurasi untuk Java 17, yang diperlukan oleh
versi Jetpack Compose dan Hilt, tambahkan kode berikut ke file app/build.gradle:
Kotlin
android { ... compileOptions { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } }
Groovy
android { ... compileOptions { sourceCompatibility JavaVersion.VERSION_17 targetCompatibility JavaVersion.VERSION_17 } }
Class aplikasi Hilt
Semua aplikasi yang menggunakan Hilt harus berisi class
Application yang dianotasi dengan
@HiltAndroidApp.
@HiltAndroidApp memicu pembuatan kode Hilt, termasuk class dasar untuk
aplikasi Anda yang berfungsi sebagai container dependensi tingkat aplikasi.
@HiltAndroidApp class ExampleApplication : Application() { ... }
Komponen Hilt yang dihasilkan ini dipasang ke siklus proses
objek Application dan menyediakan dependensi ke objek tersebut. Selain itu, komponen ini adalah komponen induk dari aplikasi, yang berarti bahwa komponen lain dapat mengakses
dependensi yang disediakannya.
Menginjeksikan dependensi ke class Android
Setelah Hilt disiapkan di class Application Anda dan komponen
tingkat aplikasi tersedia, Hilt dapat menyediakan dependensi ke class Android lain
yang memiliki anotasi @AndroidEntryPoint:
@AndroidEntryPoint class ExampleActivity : ComponentActivity() { ... }
Saat ini Hilt mendukung class Android berikut:
Application(dengan menggunakan@HiltAndroidApp)ViewModel(dengan menggunakan@HiltViewModel)ActivityServiceBroadcastReceiver
Di Compose, Anda tidak perlu membuat anotasi pada setiap composable. Sebagai gantinya,
anotasikan ComponentActivity root Anda dengan @AndroidEntryPoint. Hal ini berfungsi
sebagai titik entri DI tunggal untuk seluruh hierarki UI, sehingga Anda dapat mengakses
ViewModel yang di-inject Hilt secara langsung dalam fungsi composable.
@AndroidEntryPoint menghasilkan komponen Hilt individual untuk setiap class
Android dalam project Anda. Komponen ini dapat menerima dependensi dari class induk mereka
masing-masing, seperti yang dijelaskan dalam Hierarki
komponen.
Untuk mendapatkan dependensi dari komponen, gunakan anotasi @Inject untuk melakukan
injeksi kolom:
@AndroidEntryPoint class ExampleActivity : ComponentActivity() { @Inject lateinit var analytics: AnalyticsAdapter ... }
Class yang diinjeksikan oleh Hilt dapat memiliki class dasar lainnya yang juga menggunakan injeksi.
Class tersebut tidak memerlukan anotasi @AndroidEntryPoint jika bersifat
abstrak.
Untuk mempelajari lebih lanjut callback siklus proses tempat class Android diinjeksikan, lihat Masa aktif komponen.
Menentukan binding Hilt
Untuk melakukan injeksi kolom, Hilt perlu mengetahui cara menyediakan instance dependensi yang diperlukan dari komponen yang sesuai. Binding berisi informasi yang diperlukan untuk menyediakan instance suatu jenis sebagai dependensi.
Salah satu cara untuk memberikan informasi binding ke Hilt adalah injeksi konstruktor. Gunakan
anotasi @Inject pada konstruktor class untuk memberi tahu Hilt cara
menyediakan instance class tersebut:
class AnalyticsAdapter @Inject constructor( private val service: AnalyticsService ) { ... }
Parameter dari konstruktor anotasi sebuah class adalah dependensi
class tersebut. Dalam contoh ini, AnalyticsAdapter memiliki AnalyticsService sebagai
dependensi. Oleh karena itu, Hilt juga harus mengetahui cara menyediakan instance
AnalyticsService.
Modul Hilt
Terkadang suatu jenis tidak dapat di-injeksikan melalui konstruktor. Hal ini dapat terjadi karena beberapa alasan. Misalnya, Anda tidak dapat menginjeksikan antarmuka melalui konstruktor. Anda juga tidak dapat menginjeksikan jenis yang tidak Anda miliki melalui konstruktor, seperti class dari library eksternal. Dalam hal ini, Anda dapat memberikan informasi binding kepada Hilt dengan menggunakan modul Hilt.
Modul Hilt adalah class yang dianotasi dengan @Module. Ini memberikan petunjuk kepada Hilt tentang cara membuat instance jenis yang tidak dapat disediakan melalui injeksi konstruktor, seperti antarmuka atau class pihak ketiga. Anda juga harus
menganotasi setiap modul dengan @InstallIn untuk memberi tahu Hilt class
Android mana yang akan digunakan atau dipasang dalam setiap modul.
Dependensi yang Anda berikan di modul Hilt tersedia di semua komponen yang dihasilkan dan dikaitkan dengan class Android tempat Anda memasang modul Hilt.
Menginjeksikan instance antarmuka dengan @Binds
Pertimbangkan contoh AnalyticsService. Jika AnalyticsService adalah antarmuka,
Anda tidak dapat menginjeksikannya melalui konstruktor. Sebagai gantinya, berikan informasi
binding kepada Hilt dengan cara membuat fungsi abstrak yang dianotasi dengan @Binds di dalam
modul Hilt.
Anotasi @Binds akan memberi tahu Hilt implementasi mana yang akan digunakan ketika Hilt harus
menyediakan instance antarmuka.
Fungsi yang dianotasi menyediakan informasi berikut kepada Hilt:
- Fungsi jenis nilai yang ditampilkan memberi tahu Hilt antarmuka mana yang instance-nya disediakan oleh fungsi.
- Parameter fungsi memberi tahu Hilt implementasi mana yang harus disediakan.
interface AnalyticsService { fun analyticsMethods() } // Constructor-injected, because Hilt needs to know how to // provide instances of AnalyticsServiceImpl, too. class AnalyticsServiceImpl @Inject constructor( ... ) : AnalyticsService { ... } @Module @InstallIn(ActivityComponent::class) abstract class AnalyticsModule { @Binds abstract fun bindAnalyticsService( analyticsServiceImpl: AnalyticsServiceImpl ): AnalyticsService }
Modul Hilt AnalyticsModule dianotasi dengan
@InstallIn(ActivityComponent.class) karena Anda ingin Hilt menginjeksikan dependensi tersebut
ke ExampleActivity. Anotasi ini berarti bahwa semua
dependensi dalam AnalyticsModule tersedia di semua aktivitas aplikasi.
Menginjeksikan instance dengan @Provides
Antarmuka bukanlah satu-satunya kasus saat Anda tidak dapat memasukkan jenis melalui konstruktor.
Injeksi Konstruktor juga tidak dapat dilakukan jika Anda bukan pemilik class karena
class itu berasal dari library eksternal (class seperti
Retrofit,
OkHttpClient,
atau database Room), atau jika instance harus
dibuat dengan pola
builder.
Pertimbangkan contoh sebelumnya. Jika Anda tidak secara langsung memiliki class
AnalyticsService, Anda dapat memberi tahu Hilt cara menyediakan instance jenis ini dengan cara membuat
fungsi di dalam modul Hilt dan menganotasi fungsi itu dengan @Provides.
Fungsi yang dianotasi menyediakan informasi berikut kepada Hilt:
- Fungsi jenis nilai yang ditampilkan memberi tahu Hilt antarmuka mana yang instance-nya disediakan oleh fungsi.
- Parameter fungsi memberi tahu Hilt dependensi dari jenis yang sesuai.
- Isi fungsi memberi tahu Hilt cara menyediakan instance dari jenis yang sesuai. Hilt menjalankan isi fungsi setiap kali harus menyediakan instance dari jenis tersebut.
@Module @InstallIn(ActivityComponent::class) object AnalyticsModule { @Provides fun provideAnalyticsService( // Potential dependencies of this type ): AnalyticsService { return Retrofit.Builder() .baseUrl("https://example.com") .build() .create(AnalyticsService::class.java) } }
Menyediakan beberapa binding untuk jenis yang sama
Ketika Anda membutuhkan Hilt untuk menyediakan berbagai implementasi dari jenis yang sama sebagai dependensi, Anda harus memberikan beberapa binding kepada Hilt. Anda dapat menentukan beberapa binding untuk jenis yang sama dengan penentu.
Penentu adalah anotasi yang Anda gunakan agar dapat mengidentifikasi binding tertentu untuk suatu jenis ketika jenis itu memiliki beberapa binding yang telah ditetapkan.
Pertimbangkan contohnya. Jika Anda perlu meng-intercept panggilan ke AnalyticsService, Anda
dapat menggunakan objek OkHttpClient dengan
interceptor. Untuk
layanan lain, Anda mungkin perlu meng-intercept panggilan dengan cara lain. Dalam hal
ini, Anda perlu memberi tahu Hilt cara menyediakan dua implementasi
OkHttpClient yang berbeda.
Pertama, tentukan penentu yang akan Anda gunakan untuk menganotasi metode @Binds atau
@Provides:
@Qualifier @Retention(AnnotationRetention.BINARY) annotation class AuthInterceptorOkHttpClient @Qualifier @Retention(AnnotationRetention.BINARY) annotation class OtherInterceptorOkHttpClient
Kemudian, Hilt perlu mengetahui cara menyediakan instance dari jenis yang sesuai
dengan setiap penentu. Dalam hal ini, Anda dapat menggunakan modul Hilt dengan @Provides.
Kedua metode memiliki persamaan dalam jenis nilai yang ditampilkan, tetapi penentu melabelinya sebagai dua
binding yang berbeda:
@Module @InstallIn(SingletonComponent::class) object NetworkModule { @AuthInterceptorOkHttpClient @Provides fun provideAuthInterceptorOkHttpClient( authInterceptor: AuthInterceptor ): OkHttpClient { return OkHttpClient.Builder() .addInterceptor(authInterceptor) .build() } @OtherInterceptorOkHttpClient @Provides fun provideOtherInterceptorOkHttpClient( otherInterceptor: OtherInterceptor ): OkHttpClient { return OkHttpClient.Builder() .addInterceptor(otherInterceptor) .build() } }
Anda dapat menginjeksikan jenis tertentu yang dibutuhkan dengan menganotasi kolom atau parameter dengan penentu yang sesuai:
// As a dependency of another class. @Module @InstallIn(ActivityComponent::class) object AnalyticsModule { @Provides fun provideAnalyticsService( @AuthInterceptorOkHttpClient okHttpClient: OkHttpClient ): AnalyticsService { return Retrofit.Builder() .baseUrl("https://example.com") .client(okHttpClient) .build() .create(AnalyticsService::class.java) } } // As a dependency of a constructor-injected class. class ExampleServiceImpl @Inject constructor( @AuthInterceptorOkHttpClient private val okHttpClient: OkHttpClient ) : ... // At field injection. @AndroidEntryPoint class ExampleActivity: ComponentActivity() { @AuthInterceptorOkHttpClient @Inject lateinit var okHttpClient: OkHttpClient }
Sebagai praktik terbaik, jika Anda menambahkan penentu ke suatu jenis, tambahkan penentu ke semua cara yang memungkinkan untuk menyediakan dependensi tersebut. Membiarkan implementasi dasar atau umum tanpa penentu akan rawan terhadap error dan dapat menyebabkan Hilt memasukkan dependensi yang salah.
Penentu yang telah ditetapkan dalam Hilt
Hilt menyediakan beberapa penentu yang telah ditetapkan. Misalnya, karena Anda mungkin memerlukan
class Context dari aplikasi atau aktivitas, Hilt menyediakan
penentu @ApplicationContext dan @ActivityContext.
Misalkan class AnalyticsAdapter dari contoh membutuhkan konteks
aktivitas. Kode berikut menunjukkan cara menyediakan konteks
aktivitas untuk AnalyticsAdapter:
class AnalyticsAdapter @Inject constructor( @ActivityContext private val context: Context, private val service: AnalyticsService ) { ... }
Untuk binding lain yang telah ditetapkan sebelumnya dan tersedia di Hilt, lihat Binding default komponen.
Komponen yang dihasilkan untuk class Android
Untuk setiap class Android tempat Anda dapat melakukan injeksi kolom, ada
komponen Hilt terkait yang dapat Anda lihat dalam anotasi @InstallIn.
Setiap komponen Hilt bertanggung jawab untuk menginjeksikan binding ke
kelas Android yang sesuai.
Contoh sebelumnya menunjukkan penggunaan ActivityComponent modul
dalam Hilt.
Hilt memberikan komponen berikut:
| Komponen Hilt | Injektor untuk |
|---|---|
SingletonComponent |
Application |
ActivityRetainedComponent |
T/A |
ViewModelComponent |
ViewModel |
ActivityComponent |
Activity |
ServiceComponent |
Service |
Masa aktif komponen
Hilt secara otomatis membuat dan menghancurkan instance dari class komponen yang dihasilkan setelah siklus proses class Android terkait.
| Komponen yang dihasilkan | Dibuat pada | Dihancurkan pada |
|---|---|---|
SingletonComponent |
Application#onCreate() |
Application dihancurkan |
ActivityRetainedComponent |
Activity#onCreate() |
Activity#onDestroy() |
ViewModelComponent |
ViewModel dibuat |
ViewModel dihancurkan |
ActivityComponent |
Activity#onCreate() |
Activity#onDestroy() |
ServiceComponent |
Service#onCreate() |
Service#onDestroy() |
Cakupan komponen
Secara default, semua binding di Hilt adalah tanpa cakupan. Ini berarti setiap kali aplikasi Anda meminta binding, Hilt membuat instance baru dari jenis yang diperlukan.
Dalam contoh ini, setiap kali Hilt memberikan AnalyticsAdapter sebagai dependensi ke
jenis lain atau melalui injeksi kolom (seperti di ExampleActivity), Hilt memberikan
instance baru AnalyticsAdapter.
Namun, Hilt juga memungkinkan binding untuk tercakup dalam komponen tertentu. Hilt hanya membuat binding cakupan sekali saja per instance dari komponen tempat binding tercakup, dan semua permintaan untuk binding itu memiliki instance yang sama.
Tabel di bawah mencantumkan anotasi cakupan untuk setiap komponen yang dihasilkan:
| Class Android | Komponen yang dihasilkan | Cakupan |
|---|---|---|
Application |
SingletonComponent |
@Singleton |
Activity |
ActivityRetainedComponent |
@ActivityRetainedScoped |
ViewModel |
ViewModelComponent |
@ViewModelScoped |
Activity |
ActivityComponent |
@ActivityScoped |
Service |
ServiceComponent |
@ServiceScoped |
Dalam contoh ini, jika Anda cakupkan AnalyticsAdapter ke ActivityComponent
menggunakan @ActivityScoped, Hilt akan memberikan instance AnalyticsAdapter
yang sama selama masa aktif aktivitas yang terkait:
@ActivityScoped class AnalyticsAdapter @Inject constructor( private val service: AnalyticsService ) { ... }
Misalkan AnalyticsService memiliki status internal yang mewajibkan instance yang sama
digunakan setiap saat—bukan hanya di ExampleActivity, tetapi di mana saja dalam
aplikasi. Dalam hal ini, cakupan AnalyticsService sesuai dengan
SingletonComponent. Hasilnya, setiap kali komponen perlu
menyediakan instance AnalyticsService, komponen akan menyediakan instance yang sama setiap
saat.
Contoh berikut menunjukkan cara mengatur cakupan binding ke komponen dalam
modul Hilt. Cakupan binding harus cocok dengan cakupan komponen tempatnya
diinstal, sehingga dalam contoh ini Anda harus menginstal AnalyticsService di
SingletonComponent, bukan ActivityComponent:
// If AnalyticsService is an interface. @Module @InstallIn(SingletonComponent::class) abstract class AnalyticsModule { @Singleton @Binds abstract fun bindAnalyticsService( analyticsServiceImpl: AnalyticsServiceImpl ): AnalyticsService } // If you don't own AnalyticsService. @Module @InstallIn(SingletonComponent::class) object AnalyticsModule { @Singleton @Provides fun provideAnalyticsService(): AnalyticsService { return Retrofit.Builder() .baseUrl("https://example.com") .build() .create(AnalyticsService::class.java) } }
Untuk mempelajari cakupan komponen Hilt lebih lanjut, lihat Cakupan di Android dan Hilt.
Hierarki komponen
Menginstal modul ke dalam komponen memungkinkan binding-nya untuk diakses sebagai dependensi binding lain dalam komponen itu atau dalam komponen turunan di bawahnya dalam hierarki komponen.
Binding default komponen
Setiap komponen Hilt dilengkapi dengan serangkaian binding default yang dapat diinjeksikan oleh Hilt sebagai dependensi ke binding kustom Anda sendiri. Perhatikan bahwa binding ini sesuai dengan jenis aktivitas umum, bukan dengan subclass tertentu. Ini karena Hilt menggunakan definisi komponen aktivitas tunggal untuk menginjeksikan semua aktivitas. Setiap aktivitas memiliki instance yang berbeda dari komponen ini.
| Komponen Android | Binding default |
|---|---|
SingletonComponent |
Application |
ActivityRetainedComponent |
Application |
ViewModelComponent |
SavedStateHandle |
ActivityComponent |
Application, Activity |
ServiceComponent |
Application, Service |
Binding konteks aplikasi juga tersedia menggunakan @ApplicationContext.
Contoh:
class AnalyticsServiceImpl @Inject constructor( @ApplicationContext context: Context ) : AnalyticsService { ... } // The Application binding is available without qualifiers. class AnalyticsServiceImpl @Inject constructor( application: Application ) : AnalyticsService { ... }
Binding konteks aktivitas juga tersedia menggunakan @ActivityContext. Contoh:
class AnalyticsAdapter @Inject constructor( @ActivityContext context: Context ) { ... } // The Activity binding is available without qualifiers. class AnalyticsAdapter @Inject constructor( activity: ComponentActivity ) { ... }
Menginjeksikan dependensi dalam class yang tidak didukung oleh Hilt
Di Compose, pola standar adalah menyuntikkan dependensi ke dalam
@HiltViewModel menggunakan injeksi konstruktor, lalu menggunakan hiltViewModel()
di dalam composable untuk mengakses ViewModel. Meskipun Hilt mendukung class Android yang paling umum, Anda mungkin masih menemukan class yang tidak didukung dan memerlukan injeksi kolom.
Dalam kasus itu, Anda dapat membuat titik entri menggunakan anotasi
@EntryPoint. Titik entri adalah batas antara kode yang dikelola oleh Hilt
dan yang tidak. Ini adalah titik di mana kode pertama kali masuk ke grafik
objek yang dikelola Hilt. Titik masuk memungkinkan Hilt untuk menggunakan kode yang tidak
dikelola Hilt untuk menyediakan dependensi dalam grafik dependensi.
Misalnya, Hilt tidak mendukung penyedia
konten secara langsung. Jika Anda ingin penyedia
konten menggunakan Hilt untuk mendapatkan beberapa dependensi, Anda perlu menentukan antarmuka
yang dianotasi dengan @EntryPoint untuk setiap jenis binding yang diinginkan dan
menyertakan penentu. Lalu tambahkan @InstallIn untuk menentukan komponen tempat
menginstal titik masuk sebagai berikut:
class ExampleContentProvider : ContentProvider() { @EntryPoint @InstallIn(SingletonComponent::class) interface ExampleContentProviderEntryPoint { fun analyticsService(): AnalyticsService } ... }
Untuk mengakses titik masuk, gunakan metode statis yang sesuai dari
EntryPointAccessors. Parameter harus berupa instance komponen atau
objek @AndroidEntryPoint yang berfungsi sebagai pemegang komponen. Pastikan
bahwa komponen yang Anda teruskan sebagai parameter dan metode
statis EntryPointAccessors cocok dengan class Android dalam anotasi @InstallIn pada
antarmuka @EntryPoint:
class ExampleContentProvider: ContentProvider() { ... override fun query(...): Cursor { val appContext = context?.applicationContext ?: throw IllegalStateException() val hiltEntryPoint = EntryPointAccessors.fromApplication(appContext, ExampleContentProviderEntryPoint::class.java) val analyticsService = hiltEntryPoint.analyticsService() ... } }
Dalam contoh ini, Anda harus menggunakan ApplicationContext untuk mengambil titik
masuk karena titik masuk diinstal di SingletonComponent. Jika
binding yang ingin Anda ambil berada di ActivityComponent, Anda perlu
menggunakan ActivityContext sebagai gantinya.
Hilt dan Dagger
Hilt adalah library yang direkomendasikan secara resmi untuk injeksi dependensi di Android. Library ini menyediakan cara yang standar, berpendapat, dan efisien untuk menerapkan injeksi dependensi di aplikasi Anda, yang dioptimalkan secara khusus untuk Jetpack Compose dan arsitektur satu aktivitas.
Tujuan Hilt adalah sebagai berikut:
- Membuat serangkaian komponen dan cakupan standar untuk memudahkan penyiapan, keterbacaan, dan berbagi kode antar-aplikasi.
- Memberikan cara yang mudah dalam menyediakan binding yang berbeda untuk berbagai jenis build, seperti pengujian, debug, atau rilis.
Karena sistem operasi Android membuat instance untuk berbagai class framework-nya sendiri, menggunakan Dagger di aplikasi Android mengharuskan Anda untuk menulis sejumlah besar boilerplate. Hilt mengurangi kode boilerplate yang terlibat dalam penggunaan Dagger pada aplikasi Android. Secara otomatis Hilt menghasilkan dan menyediakan hal berikut:
- Komponen untuk mengintegrasikan class framework Android dengan Dagger yang seharusnya Anda buat sendiri.
- Anotasi cakupan yang akan digunakan bersama komponen yang dihasilkan Hilt secara otomatis.
- Binding yang telah ditetapkan sebelumnya untuk mewakili class Android seperti
ApplicationatauActivity. - Penentu yang telah ditetapkan sebelumnya untuk mewakili
@ApplicationContextdan@ActivityContext.
Kode Dagger dan Hilt dapat berdampingan dalam codebase yang sama. Namun, dalam sebagian besar kasus, sebaiknya gunakan Hilt untuk mengelola semua penggunaan Dagger di Android. Untuk memigrasikan project yang menggunakan Dagger ke Hilt, lihat panduan migrasi.
Referensi lainnya
Untuk mempelajari Hilt lebih lanjut, lihat referensi tambahan berikut.
Contoh
Blog
- Injeksi Dependensi pada Android dengan Hilt
- Cakupan di Android dan Hilt
- Menambahkan komponen ke hierarki Hilt
- Memigrasikan aplikasi Google I/O ke Hilt