Mengelola dependensi dalam aplikasi Android modern bisa jadi pekerjaan yang cukup kompleks. Dari database, API client, hingga ViewModel, semua perlu di-instantiate dan disuntikkan (injected) ke komponen lain dengan benar. Di sinilah Dependency Injection (DI) berperan, dan bagi developer Android dengan Kotlin, Hilt hadir sebagai solusi yang elegan dan powerful.
Hilt adalah library DI yang dibangun di atas Dagger, dirancang khusus untuk aplikasi Android. Tujuannya sederhana: menyederhanakan penggunaan Dagger dan mengurangi boilerplate code yang sering membuat developer frustrasi. Kalau Anda pernah pusing dengan Dagger, Hilt adalah angin segar yang akan membuat hidup Anda lebih mudah. Dalam panduan lengkap ini, kita akan menyelami cara menggunakan Hilt di project Kotlin Android Anda, mulai dari setup dasar hingga praktik terbaik di dunia nyata.
Apa itu Hilt dan Mengapa Penting untuk Developer Android?
Hilt adalah standar rekomendasi untuk dependency injection di Android. Sebagai wrapper di atas Dagger, Hilt mengambil banyak konsep powerful dari Dagger, namun dengan abstraksi yang lebih tinggi dan integrasi yang lebih erat dengan komponen Android lifecycle. Ini berarti Anda bisa fokus pada logika bisnis aplikasi Anda, bukan pada cara mengelola grafik dependensi.
Mengapa Hilt menjadi pilihan utama developer modern?
- Sederhana dan Mengurangi Boilerplate: Jika Anda pernah mencoba Dagger murni, Anda tahu betapa banyaknya kelas dan konfigurasi yang perlu ditulis. Hilt meminimalkan semua itu dengan otomatis menghasilkan banyak kode DI yang diperlukan.
- Integrasi dengan Komponen Android: Hilt secara otomatis menyediakan komponen DI untuk setiap kelas Android di proyek Anda, seperti
Activity,Fragment,Service, danViewModel, dan mengelola lifecycle-nya. - Testabilitas Lebih Baik: Dengan dependensi yang disuntikkan, Anda bisa dengan mudah menukar implementasi dependensi untuk pengujian, membuat unit dan instrumentasi testing menjadi jauh lebih sederhana.
- Standar Industri: Hilt didukung oleh Google, menjadikannya pilihan yang solid dan akan terus mendapatkan dukungan serta pembaruan.
Persiapan Project: Menambahkan Hilt ke Aplikasi Android Anda
Sebelum kita mulai menggunakannya, kita perlu menambahkan Hilt ke project Android Anda. Prosesnya cukup straightforward:
Langkah 1: Tambahkan Dependency di file build.gradle
Pertama, tambahkan plugin Hilt dan dependensi ke file build.gradle di level project dan modul aplikasi Anda.
Buka file build.gradle (Project: your_project_name):
Pastikan terdapat repositori Maven Google dan tambahkan plugin Hilt di bagian plugins:
// build.gradle (Project: your_project_name)
plugins {
id 'com.android.application' version '8.2.2' apply false
id 'org.jetbrains.kotlin.android' version '1.9.0' apply false
// Tambahkan baris ini
id 'com.google.dagger.hilt.android' version '2.50' apply false
}
Selanjutnya, buka file build.gradle (Module: app). Tambahkan plugin Hilt di bagian atas dan dependensi Hilt di bagian dependencies:
// build.gradle (Module: app)
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
// Tambahkan baris ini
id 'com.google.dagger.hilt.android'
// Plugin untuk anotasi Kotlin KAPT
id 'kotlin-kapt'
}
android {
// ...
}
dependencies {
// Core Hilt
implementation 'com.google.dagger:hilt-android:2.50'
kapt 'com.google.dagger:hilt-compiler:2.50'
// Hilt untuk ViewModel (jika menggunakan Jetpack ViewModel)
implementation 'androidx.hilt:hilt-navigation-fragment:1.1.0'
implementation 'androidx.hilt:hilt-work:1.1.0' // opsional untuk WorkManager
kapt 'androidx.hilt:hilt-compiler:1.1.0'
// ... dependensi lainnya
}
Pastikan untuk melakukan “Sync Project with Gradle Files” setelah menambahkan dependensi.
Langkah 2: Buat Application Class dan Anotasi @HiltAndroidApp
Hilt perlu tahu di mana ia harus memulai proses DI. Ini dilakukan dengan menganotasi kelas Application Anda dengan @HiltAndroidApp. Jika Anda belum punya custom Application class, ini saatnya membuatnya.
// MyApp.kt
package com.tubianto.myapp
import android.app.Application
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
class MyApp : Application() {
// Anda bisa menambahkan inisialisasi lain di sini jika diperlukan
override fun onCreate() {
super.onCreate()
// Misalnya, inisialisasi crash reporting atau library lainnya
}
}
Jangan lupa daftarkan kelas MyApp ini di file AndroidManifest.xml Anda:
<!-- AndroidManifest.xml -->
<application
android:name=".MyApp" <!-- Tambahkan ini -->
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MyApp">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
Sampai di sini, setup dasar Hilt sudah selesai. Sekarang aplikasi Anda siap untuk menggunakan Dependency Injection.
Memahami Komponen Dasar Hilt untuk Dependency Injection
Untuk menggunakan Hilt secara efektif, ada beberapa anotasi kunci yang perlu Anda pahami:
1. @Inject: Meminta Dependensi
Anotasi @Inject digunakan untuk memberitahu Hilt bahwa Anda ingin dependensi tertentu disuntikkan. Ini bisa digunakan pada konstruktor, field, atau method.
-
Constructor Injection: Ini adalah cara paling umum dan direkomendasikan. Hilt akan secara otomatis menyediakan instance kelas ketika Anda menganotasi konstruktornya.
class MyRepository @Inject constructor( private val apiService: ApiService ) { // ... } -
Field Injection: Digunakan untuk komponen Android seperti
ActivityatauFragment, di mana Anda tidak memiliki kontrol atas konstruktor.class MyActivity : AppCompatActivity() { @Inject lateinit var myRepository: MyRepository // ... }Penting: Field yang di-inject dengan
@Injecttidak bolehprivate.
2. @AndroidEntryPoint: Titik Masuk Hilt ke Komponen Android
Setiap komponen Android (Activity, Fragment, Service, BroadcastReceiver, View) yang akan menerima injection dari Hilt harus dianotasi dengan @AndroidEntryPoint.
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
@Inject lateinit var myRepository: MyRepository
// ...
}
@AndroidEntryPoint
class MyFragment : Fragment() {
// ...
}
Ketika Anda menganotasi sebuah komponen dengan @AndroidEntryPoint, Hilt akan membuat komponen Dagger secara otomatis untuknya, yang akan digunakan untuk menyuntikkan dependensi.
3. @Module dan @Provides: Menyediakan Dependensi yang Kompleks
Tidak semua dependensi bisa di-inject langsung melalui konstruktor. Misalnya, interface, kelas dari library pihak ketiga yang tidak bisa Anda modifikasi, atau instance yang membutuhkan konfigurasi khusus (seperti Retrofit atau Room Database). Untuk kasus ini, Anda perlu memberitahu Hilt bagaimana cara membuat instance dependensi tersebut menggunakan @Module dan @Provides.
@Module
@InstallIn(SingletonComponent::class) // Kita akan bahas ini sebentar lagi
object NetworkModule {
@Provides
fun provideOkHttpClient(): OkHttpClient {
return OkHttpClient.Builder()
// .addInterceptor(...)
.build()
}
@Provides
fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.baseUrl("https://api.example.com/")
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
@Provides
fun provideApiService(retrofit: Retrofit): ApiService {
return retrofit.create(ApiService::class.java)
}
}
Dalam contoh di atas, NetworkModule adalah sebuah objek yang berisi fungsi-fungsi @Provides. Setiap fungsi @Provides bertanggung jawab untuk menyediakan satu jenis dependensi. Hilt akan secara otomatis mencari dependensi yang dibutuhkan oleh fungsi @Provides (misalnya, OkHttpClient dibutuhkan oleh provideRetrofit) dan menyediakannya.
4. @InstallIn: Mengaitkan Module ke Hilt Component
Hilt memiliki serangkaian komponen yang secara otomatis terkait dengan lifecycle komponen Android. @InstallIn digunakan untuk memberitahu Hilt di komponen mana sebuah @Module harus tersedia. Beberapa komponen umum:
SingletonComponent::class: Dependensi yang tersedia selama lifecycle aplikasi. Hanya satu instance.ActivityRetainedComponent::class: Dependensi yang bertahan selama konfigurasi ulangActivity(misalnya, rotasi layar).ActivityComponent::class: Dependensi yang tersedia selama lifecycleActivity.FragmentComponent::class: Dependensi yang tersedia selama lifecycleFragment.ViewModelComponent::class: Dependensi yang tersedia untukViewModel.
Jika Anda ingin sebuah dependensi tersedia di seluruh aplikasi, Anda akan menginstal modulnya di SingletonComponent::class, seperti pada contoh NetworkModule di atas.
5. @Singleton dan Scoping Lainnya: Mengelola Masa Hidup Dependensi
Seringkali Anda ingin sebuah dependensi hanya memiliki satu instance di seluruh aplikasi (misalnya, database client atau API service). Anda bisa mencapainya dengan @Singleton.
@Singleton // Hanya akan ada satu instance OkHttpClient di seluruh aplikasi
@Provides
fun provideOkHttpClient(): OkHttpClient {
// ...
}
Selain @Singleton, Hilt juga menyediakan anotasi scoping lain seperti @ActivityScoped, @FragmentScoped, dll., yang memastikan dependensi memiliki masa hidup yang sama dengan komponen tempat ia diinstal.
Studi Kasus: Menggunakan Hilt dalam Praktik untuk Project Android
Mari kita lihat bagaimana Hilt digunakan dalam skenario yang lebih realistis. Kita akan membuat sebuah aplikasi sederhana yang menampilkan daftar item dari API.
Skenario: Menampilkan Daftar Data dari Remote API
Kita akan mengimplementasikan:
ApiServiceuntuk interaksi API.MyRepositorysebagai abstraksi data.MyViewModeluntuk logika UI.MainActivityuntuk menampilkan data.
Semua dependensi akan disuntikkan oleh Hilt.
Langkah 1: Definisikan Kontrak Data dan Layanan API
// Data.kt
package com.tubianto.myapp.data
data class Item(
val id: String,
val name: String,
val description: String
)
// ApiService.kt
package com.tubianto.myapp.data.remote
import com.tubianto.myapp.data.Item
import retrofit2.Response
import retrofit2.http.GET
interface ApiService {
@GET("items") // Asumsikan ada endpoint /items
suspend fun getItems(): Response<List<Item>>
}
Langkah 2: Buat Modul untuk Kebutuhan Jaringan (NetworkModule)
Kita akan membuat instance Retrofit dan ApiService di sini.
// NetworkModule.kt
package com.tubianto.myapp.di
import com.tubianto.myapp.data.remote.ApiService
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@Singleton
@Provides
fun provideOkHttpClient(): OkHttpClient {
return OkHttpClient.Builder()
// Tambahkan interceptor logging jika diperlukan untuk debugging
// .addInterceptor(HttpLoggingInterceptor().apply {
// level = HttpLoggingInterceptor.Level.BODY
// })
.build()
}
@Singleton
@Provides
fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.baseUrl("https://api.example.com/") // Ganti dengan URL API Anda
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
@Singleton
@Provides
fun provideApiService(retrofit: Retrofit): ApiService {
return retrofit.create(ApiService::class.java)
}
}
Langkah 3: Buat Repository
Repository akan menerima ApiService melalui constructor injection.
// MyRepository.kt
package com.tubianto.myapp.data
import com.tubianto.myapp.data.remote.ApiService
import javax.inject.Inject
import javax.inject.Singleton
@Singleton // Repository ini juga bisa di-scope sebagai Singleton
class MyRepository @Inject constructor(
private val apiService: ApiService
) {
suspend fun getItems(): List<Item> {
val response = apiService.getItems()
if (response.isSuccessful && response.body() != null) {
return response.body()!!
}
// Handle error atau kembalikan list kosong
return emptyList()
}
}
Langkah 4: Buat ViewModel dengan @HiltViewModel
Hilt menyediakan anotasi khusus @HiltViewModel untuk ViewModel.
// MyViewModel.kt
package com.tubianto.myapp.ui.main
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.tubianto.myapp.data.Item
import com.tubianto.myapp.data.MyRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class MyViewModel @Inject constructor(
private val repository: MyRepository
) : ViewModel() {
private val _items = MutableStateFlow<List<Item>>(emptyList())
val items: StateFlow<List<Item>> = _items
init {
fetchItems()
}
fun fetchItems() {
viewModelScope.launch {
_items.value = repository.getItems()
}
}
}
Langkah 5: Inject di Activity (atau Fragment)
Akhirnya, kita akan menyuntikkan ViewModel ke MainActivity.
// MainActivity.kt
package com.tubianto.myapp.ui.main
import android.os.Bundle
import android.widget.TextView
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import com.tubianto.myapp.R
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private val viewModel: MyViewModel by viewModels() // Hilt akan menyediakan ViewModel
private lateinit var itemTextView: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
itemTextView = findViewById(R.id.itemTextView)
lifecycleScope.launch {
viewModel.items.collect { items ->
if (items.isNotEmpty()) {
itemTextView.text = "Fetched Items:\n" +
items.joinToString("\n") { "${it.id}: ${it.name}" }
} else {
itemTextView.text = "No items fetched or error occurred."
}
}
}
}
}
Dengan Hilt, Anda tidak perlu lagi membuat factory untuk ViewModel Anda. Cukup gunakan by viewModels() dengan @HiltViewModel dan @AndroidEntryPoint, Hilt akan mengurus sisanya.
Pengalaman dan Pertimbangan Praktis Menggunakan Hilt
Sebagai seorang developer yang sudah banyak berkutat dengan DI di Android, saya bisa katakan Hilt adalah game changer. Berikut beberapa observasi dan tips praktis:
Kelebihan Hilt dalam Project Nyata:
- Produktivitas Meningkat: Waktu yang dihabiskan untuk menulis boilerplate Dagger berkurang drastis. Ini memungkinkan tim fokus pada fitur, bukan infrastruktur.
- Kode Lebih Bersih dan Mudah Dibaca: Dengan anotasi yang ringkas, intent DI menjadi lebih jelas. Developer baru di tim bisa lebih cepat memahami bagaimana dependensi diatur.
- Mencegah Kesalahan Umum: Integrasi erat dengan komponen Android membantu mencegah kesalahan terkait lifecycle atau scoping yang sering terjadi dengan Dagger murni.
- Testing Lebih Efisien: Hilt membuat proses testing unit dan instrumentasi menjadi sangat mudah karena dependensi dapat diganti (mocked) dengan minimal usaha.
Keterbatasan dan Trade-off:
- Kurva Pembelajaran Awal (tetap ada): Meskipun lebih mudah dari Dagger, konsep DI dan anotasi Hilt tetap butuh waktu untuk dipahami, terutama bagi pemula.
- Potensi Ukuran APK Lebih Besar: Karena Hilt menghasilkan banyak kode, ukuran APK bisa sedikit bertambah. Namun, biasanya ini tidak signifikan untuk project modern.
- Debugging: Saat terjadi masalah DI, pesan error dari Dagger/Hilt bisa sedikit menantang untuk diinterpretasikan di awal. Memahami grafik dependensi menjadi kunci.
Best Practices:
- Organisasi Modul: Kelompokkan modul berdasarkan fungsionalitas (misalnya,
NetworkModule,DatabaseModule,AppModule). - Scoping yang Tepat: Jangan menganggap semua harus
@Singleton. Pertimbangkan masa hidup dependensi. Misalnya, dependensi yang hanya relevan untuk satu fitur bisa menggunakan@ActivityScopedatau@FragmentScoped. - Testing: Manfaatkan kemampuan Hilt untuk memudahkan pengujian. Buat modul testing terpisah jika perlu untuk menyediakan implementasi mock.
- Gunakan Constructor Injection Sedapat Mungkin: Ini adalah cara paling bersih dan testable untuk DI. Field injection hanya gunakan jika tidak ada pilihan lain (misalnya, di komponen Android).
Masalah yang Sering Terjadi Saat Menggunakan Hilt
Dalam pengalaman saya mengimplementasikan Hilt di berbagai project, ada beberapa masalah umum yang sering muncul. Mengetahui ini bisa membantu Anda lebih cepat mendebug:
1. Missing @HiltAndroidApp
- Gejala: Hilt tidak bisa menemukan komponen Hilt atau error terkait inisialisasi.
- Penyebab: Lupa menganotasi kelas
ApplicationAnda dengan@HiltAndroidApp, atau lupa mendaftarkannya diAndroidManifest.xml. - Solusi: Pastikan kelas
ApplicationAnda memiliki@HiltAndroidAppdan terdaftar dengan benar di manifest sebagaiandroid:name=".YourApplicationClass".
2. Missing @AndroidEntryPoint
- Gejala: Dependensi yang di-inject di
Activity,Fragment, atau komponen Android lainnya tidak ditemukan (null), atau Hilt tidak bisa mengompilasi. - Penyebab: Lupa menambahkan
@AndroidEntryPointke komponen Android yang mencoba menerima injection. - Solusi: Tambahkan
@AndroidEntryPointke setiapActivity,Fragment,Service, atauBroadcastReceiveryang akan menggunakan Hilt untuk injection.
3. Dependensi Tidak Dapat Disediakan oleh Hilt
- Gejala: Pesan error kompilasi seperti “Cannot be provided without an @Provides-annotated method” atau “missing binding”.
- Penyebab:
- Untuk kelas yang bisa di-construct (bukan interface/library pihak ketiga), konstruktornya tidak dianotasi dengan
@Inject. - Untuk interface atau library pihak ketiga, tidak ada
@Moduledan@Providesyang memberitahu Hilt bagaimana cara membuat instance-nya. - Modul yang menyediakan dependensi tersebut tidak di-
@InstallIndi komponen Hilt yang benar (misalnya, mencoba inject singleton diActivityComponenttanpa menginstalnya diSingletonComponent).
- Untuk kelas yang bisa di-construct (bukan interface/library pihak ketiga), konstruktornya tidak dianotasi dengan
- Solusi:
- Pastikan konstruktor kelas memiliki
@Inject. - Buat
@Moduledan@Providesyang sesuai, dan pastikan anotasi@InstallInmengarah ke komponen Hilt yang tepat.
- Pastikan konstruktor kelas memiliki
4. Scoping Mismatch
- Gejala: Instance objek yang tidak konsisten, atau error kompilasi terkait scoping (misalnya, mencoba inject dependensi
@Singletonke dalam komponen dengan scope lebih kecil tanpa@ActivityScoped, dll.). - Penyebab: Anda meminta dependensi dengan scope yang berbeda dari tempat dependensi itu disediakan. Misalnya, Anda menyediakan
MyServicesebagai@ActivityScopedtetapi mencoba menggunakannya di sebuah@Singletonmodule. - Solusi: Pastikan scope dependensi cocok dengan komponen Hilt tempat ia diinjeksikan. Dependensi dengan scope lebih besar (misalnya,
@Singleton) bisa di-inject ke scope yang lebih kecil, tetapi tidak sebaliknya.
5. Tidak Sinkron dengan Gradle (KAPT Issue)
- Gejala: Error kompilasi aneh terkait Hilt atau Dagger yang tidak hilang meskipun kode sudah benar.
- Penyebab: KAPT (Kotlin Annotation Processing Tool) terkadang bisa bermasalah dengan cache.
- Solusi: Coba “Build” -> “Clean Project” lalu “Build” -> “Rebuild Project”. Kadang kala juga perlu “File” -> “Invalidate Caches / Restart…”.
FAQ
Apa bedanya Hilt dan Dagger?
Hilt adalah wrapper di atas Dagger. Dagger adalah library Dependency Injection yang kuat dan generik, bisa digunakan di project Java mana pun. Hilt secara spesifik dibangun untuk Android, menyederhanakan penggunaan Dagger dengan menyediakan komponen DI yang sudah terintegrasi dengan lifecycle komponen Android secara otomatis. Hilt mengurangi boilerplate Dagger secara signifikan untuk project Android.
Kapan saya harus menggunakan Hilt?
Hilt direkomendasikan untuk hampir semua project Android modern, terutama yang menengah hingga besar. Untuk project yang sangat kecil dengan sedikit dependensi, Anda mungkin bisa lolos dengan manual DI, tetapi bahkan di sana, Hilt tetap memberikan manfaat untuk testabilitas dan skalabilitas di masa depan.
Apakah Hilt cocok untuk project kecil?
Ya, Hilt tetap cocok untuk project kecil. Meskipun ada sedikit biaya overhead awal untuk setup, manfaatnya dalam mengurangi boilerplate dan meningkatkan testabilitas akan terasa bahkan di project kecil. Ini juga membantu Anda membangun kebiasaan baik dalam mengelola dependensi sejak dini.
Bisakah Hilt digunakan dengan Jetpack Compose?
Tentu saja! Hilt terintegrasi dengan baik di Jetpack Compose. Anda masih akan menggunakan @AndroidEntryPoint pada Activity atau Fragment yang menghosting Composable, dan @HiltViewModel untuk ViewModel Anda. Untuk Composable sendiri, Anda bisa mendapatkan instance ViewModel menggunakan hiltViewModel() atau melakukan injection ke dalam kelas yang di-inject ke Composable.
Kesimpulan
Hilt telah mengubah cara developer Android mengelola dependensi. Dengan kemampuan untuk mengurangi boilerplate, mengintegrasikan dengan lifecycle komponen Android, dan meningkatkan testabilitas, Hilt adalah alat yang tak terpisahkan dalam toolkit developer modern. Memahami konsep dasar seperti @Inject, @AndroidEntryPoint, @Module, @Provides, dan @InstallIn akan menjadi fondasi kuat Anda dalam membangun aplikasi Android yang bersih, scalable, dan mudah di-maintain.
Jangan ragu untuk mulai mengimplementasikan Hilt di project Anda berikutnya. Meskipun ada sedikit kurva pembelajaran di awal, manfaat jangka panjangnya akan jauh melebihi usaha awal. Kode Anda akan terasa lebih rapi, lebih mudah diuji, dan Anda bisa fokus pada inovasi fitur, bukan sibuk mengelola grafik dependensi.
TAGS: Hilt, Kotlin, Android, Dependency Injection, Developer Tools, Android Development, Programming Tutorial, Software Engineering, Hilt Android, Dagger


