Dalam codelab ini, Anda akan mempelajari cara memigrasikan Dagger ke Hilt untuk injeksi dependensi (DI) di aplikasi Android. Codelab ini memigrasikan Menggunakan Dagger di codelab aplikasi Android Anda ke Hilt. Codelab ini bertujuan untuk menunjukkan cara merencanakan migrasi serta menjaga Dagger dan Hilt agar berfungsi berdampingan selama migrasi dengan menjaga aplikasi tetap berfungsi saat Anda memigrasikan setiap komponen Dagger ke Hilt.
Injeksi dependensi membantu penggunaan kembali kode, kemudahan pemfaktoran ulang, dan kemudahan pengujian. 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.
Karena banyak class framework Android yang dibuatkan instance oleh OS itu sendiri, ada boilerplate terkait saat menggunakan Dagger di aplikasi Android. Hilt menghapus sebagian besar boilerplate ini dengan otomatis menghasilkan dan menyediakan:
- Komponen untuk mengintegrasikan class framework Android dengan Dagger yang seharusnya Anda buat sendiri.
- Anotasi cakupan untuk komponen yang dihasilkan Hilt secara otomatis.
- Binding dan penentu standar.
Yang terpenting, karena Dagger dan Hilt dapat bekerja berdampingan, aplikasi dapat dimigrasikan sesuai kebutuhan.
Jika Anda mengalami masalah (bug kode, kesalahan gramatikal, susunan kata yang tidak jelas, dll.) saat mengerjakan codelab ini, laporkan masalah tersebut melalui link Laporkan kesalahan di pojok kiri bawah codelab.
Prasyarat
- Berpengalaman dengan sintaksis Kotlin.
- Berpengalaman dengan Dagger.
Yang akan Anda pelajari
- Cara menambahkan Hilt ke aplikasi Android.
- Cara merencanakan strategi migrasi Anda.
- Cara memigrasikan komponen ke Hilt dan menjaga agar kode Dagger yang ada tetap berfungsi.
- Cara memigrasikan komponen tercakup.
- Cara menguji aplikasi menggunakan Hilt.
Yang Anda butuhkan
- Android Studio 4.0 atau yang lebih baru.
Mendapatkan kode
Dapatkan kode codelab dari GitHub:
$ git clone https://github.com/googlecodelabs/android-dagger-to-hilt
Atau, Anda dapat mendownload repositori sebagai file Zip:
Membuka Android Studio
Jika perlu mendownload Android Studio, Anda dapat melakukannya di sini.
Penyiapan project
Project ini dibuat di beberapa cabang GitHub:
master
adalah cabang yang Anda buka atau download. Titik awal codelab.interop
adalah cabang interop Dagger dan Hilt.solution
berisi solusi untuk codelab ini, termasuk pengujian dan ViewModels.
Sebaiknya Anda mengikuti codelab ini langkah demi langkah sesuai kemampuan Anda sendiri dimulai dengan cabang master
.
Selama codelab, Anda akan melihat cuplikan kode yang harus ditambahkan ke project. Di beberapa tempat, Anda juga harus menghapus kode yang akan disebutkan secara eksplisit dalam komentar pada cuplikan kode.
Sebagai checkpoint, Anda memiliki cabang perantara yang tersedia jika memerlukan bantuan terkait langkah tertentu.
Untuk mendapatkan cabang solution
menggunakan git, gunakan perintah ini:
$ git clone -b solution https://github.com/googlecodelabs/android-dagger-to-hilt
Atau download kode solusi dari sini:
Pertanyaan umum (FAQ)
Menjalankan aplikasi contoh
Pertama-tama, mari kita lihat bagaimana tampilan aplikasi contoh. Ikuti petunjuk ini untuk membuka aplikasi contoh di Android Studio.
- Jika Anda mendownload arsip zip, ekstrak file tersebut secara lokal.
- Buka project di Android Studio.
- Klik tombol Run , lalu pilih emulator atau hubungkan perangkat Android Anda. Layar Pendaftaran akan muncul.
Aplikasi terdiri dari 4 alur berbeda yang berfungsi dengan Dagger (diterapkan sebagai Aktivitas):
- Pendaftaran: Pengguna dapat mendaftar dengan memasukkan nama pengguna, sandi, dan menyetujui persyaratan dan ketentuan kami.
- Login: Pengguna dapat login menggunakan kredensial yang ditambahkan selama alur pendaftaran serta dapat membatalkan pendaftaran dari aplikasi.
- Beranda: Pengguna disambut dan dapat melihat jumlah notifikasi miliknya yang belum dibaca.
- Setelan: Pengguna dapat logout dan memuat ulang jumlah notifikasi yang belum dibaca (yang menghasilkan jumlah notifikasi acak).
Project ini mengikuti pola MVVM umum, dengan semua kompleksitas View dialihkan ke ViewModel. Luangkan waktu untuk membiasakan diri dengan struktur project.
Panah mewakili dependensi antar-objek. Inilah yang disebut dengan grafik aplikasi: semua class aplikasi dan dependensi di antara semua class.
Kode di cabang master
menggunakan Dagger untuk memasukkan dependensi. Daripada membuat Komponen secara manual, kami akan memfaktorkan ulang aplikasi agar menggunakan Hilt untuk menghasilkan Komponen dan kode terkait Dagger lainnya.
Dagger disiapkan di aplikasi seperti yang ditunjukkan pada diagram berikut. Titik pada jenis tertentu berarti jenis tersebut tercakup ke Komponen yang menyediakannya:
Agar mudah, dependensi Hilt telah ditambahkan ke project ini di cabang master
yang telah Anda download di awal. Anda tidak perlu menambahkan kode berikut ke project karena tindakan tersebut sudah dilakukan untuk Anda. Meskipun demikian, mari kita lihat apa yang diperlukan untuk menggunakan Hilt di aplikasi Android.
Selain dependensi library, Hilt menggunakan plugin Gradle yang dikonfigurasi dalam project. Buka file build.gradle
root (level project) dan temukan dependensi Hilt berikut dalam classpath:
buildscript {
...
ext.hilt_version = '2.28-alpha'
dependencies {
...
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
}
}
Buka app/build.gradle
dan periksa deklarasi plugin gradle Hilt di bagian atas tepat di bawah plugin kotlin-kapt
.
...
apply plugin: 'kotlin-kapt'
apply plugin: 'dagger.hilt.android.plugin'
android {
...
}
Terakhir, dependensi Hilt dan pemroses anotasi disertakan dalam project di file app/build.gradle
yang sama:
...
dependencies {
implementation "com.google.dagger:hilt-android:$hilt_version"
kapt "com.google.dagger:hilt-android-compiler:$hilt_version"
}
Semua library, termasuk Hilt, didownload saat Anda mem-build dan menyinkronkan project. Mari mulai menggunakan Hilt.
Anda mungkin tergoda untuk memigrasikan semuanya ke Hilt sekaligus, tetapi dalam project nyata, sebaiknya migrasi ke Hilt dilakukan secara bertahap agar aplikasi berhasil di-build dan berjalan tanpa error.
Saat bermigrasi ke Hilt, Anda perlu membagi prosesnya menjadi beberapa tahap. Pendekatan yang disarankan adalah memulai dengan memigrasikan Aplikasi atau komponen @Singleton
, dan kemudian memigrasikan aktivitas dan fragmen.
Dalam codelab ini, Anda akan memigrasikan AppComponent
terlebih dahulu dan kemudian setiap alur aplikasi yang dimulai dengan Pendaftaran, lalu Login, dan yang terakhir Utama dan Setelan.
Selama migrasi, Anda akan menghapus semua antarmuka @Component
dan @Subcomponent
serta menganotasi semua modul dengan @InstallIn
.
Setelah migrasi, semua class Application
/Activity
/Fragment
/View
/Service
/BroadcastReceiver
harus dianotasi dengan @AndroidEntryPoint
dan semua kode yang membuat instance atau menyebarkan komponen juga harus dihapus.
Untuk merencanakan migrasi, mari kita mulai dengan AppComponent.kt
untuk memahami hierarki komponen.
@Singleton
// Definition of a Dagger component that adds info from the different modules to the graph
@Component(modules = [StorageModule::class, AppSubcomponents::class])
interface AppComponent {
// Factory to create instances of the AppComponent
@Component.Factory
interface Factory {
// With @BindsInstance, the Context passed in will be available in the graph
fun create(@BindsInstance context: Context): AppComponent
}
// Types that can be retrieved from the graph
fun registrationComponent(): RegistrationComponent.Factory
fun loginComponent(): LoginComponent.Factory
fun userManager(): UserManager
}
AppComponent
dianotasi dengan @Component
dan mencakup dua modul, yaitu StorageModule
dan AppSubcomponents
.
AppSubcomponents
memiliki tiga komponen, yaitu RegistrationComponent
, LoginComponent
, dan UserComponent
.
LoginComponent
dimasukkan ke dalamLoginActivity
RegistrationComponent
dimasukkan ke dalamRegistrationActivity
,EnterDetailsFragment
, danTermsAndConditionsFragment
. Komponen ini juga tercakup dalamRegistrationActivity
.
UserComponent dimasukkan ke dalam MainActivity
dan SettingsActivity
.
Referensi ke ApplicationComponent
dapat diganti dengan Komponen yang dihasilkan Hilt (tertaut ke semua komponen yang dihasilkan) yang memetakan ke Komponen yang Anda migrasikan di aplikasi.
Di bagian ini, Anda akan memigrasikan AppComponent
. Anda perlu melakukan beberapa pekerjaan dasar agar kode Dagger yang ada tetap berfungsi saat memigrasikan setiap komponen ke Hilt pada langkah-langkah berikutnya.
Untuk melakukan inisialisasi Hilt dan memulai pembuatan kode, Anda perlu menganotasi class Application
dengan anotasi Hilt.
Buka MyApplication.kt
dan tambahkan anotasi @HiltAndroidApp
ke class. Anotasi ini memberi tahu Hilt untuk memicu pembuatan kode yang akan diambil dan digunakan oleh Dagger dalam pemroses anotasinya.
MyApplication.kt
package com.example.android.dagger
import android.app.Application
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
open class MyApplication : Application() {
// Instance of the AppComponent that will be used by all the Activities in the project
val appComponent: AppComponent by lazy {
initializeComponent()
}
open fun initializeComponent(): AppComponent {
// Creates an instance of AppComponent using its Factory constructor
// We pass the applicationContext that will be used as Context in the graph
return DaggerAppComponent.factory().create(applicationContext)
}
}
1. Memigrasikan modul Komponen
Untuk memulai, buka AppComponent.kt. AppComponent
memiliki dua modul (StorageModule
dan AppSubcomponents
) yang ditambahkan ke anotasi @Component
. Hal pertama yang perlu Anda lakukan adalah memigrasikan 2 modul ini, agar Hilt menambahkannya ke dalam ApplicationComponent
yang dihasilkan.
Untuk melakukannya, buka AppSubcomponents.kt dan anotasikan class dengan anotasi @InstallIn
. Anotasi @InstallIn
memerlukan parameter untuk menambahkan modul ke komponen yang tepat. Dalam hal ini, saat memigrasikan komponen tingkat aplikasi, sebaiknya binding dibuat di ApplicationComponent
.
AppSubcomponents.kt
// This module tells a Component which are its subcomponents
// Install this module in Hilt-generated ApplicationComponent
@InstallIn(ApplicationComponent::class)
@Module(
subcomponents = [
RegistrationComponent::class,
LoginComponent::class,
UserComponent::class
]
)
class AppSubcomponents
Anda harus melakukan perubahan yang sama di StorageModule
. Buka StorageModule.kt dan tambahkan anotasi @InstallIn
seperti yang dilakukan di langkah sebelumnya.
StorageModule.kt
// Tells Dagger this is a Dagger module
// Install this module in Hilt-generated ApplicationComponent
@InstallIn(ApplicationComponent::class)
@Module
abstract class StorageModule {
// Makes Dagger provide SharedPreferencesStorage when a Storage type is requested
@Binds
abstract fun provideStorage(storage: SharedPreferencesStorage): Storage
}
Dengan anotasi @InstallIn
, Anda sekali lagi memberi tahu Hilt untuk menambahkan modul ke ApplicationComponent yang dihasilkan Hilt.
Sekarang, mari kita kembali dan memeriksa AppComponent.kt. AppComponent
memberikan dependensi untuk RegistrationComponent
, LoginComponent
, dan UserManager
. Pada langkah berikutnya, Anda akan menyiapkan komponen ini untuk migrasi.
2. Memigrasikan jenis terekspos
Saat memigrasikan aplikasi ke Hilt sepenuhnya, Hilt memungkinkan Anda meminta dependensi dari Dagger melalui titik entri secara manual. Dengan menggunakan titik entri, Anda dapat menjaga aplikasi tetap berfungsi saat memigrasikan setiap komponen Dagger. Pada langkah ini, Anda akan mengganti setiap komponen Dagger dengan pencarian dependensi manual di ApplicationComponent
yang dihasilkan Hilt.
Untuk mendapatkan RegistrationComponent.Factory
untuk RegistrationActivity.kt
dari ApplicationComponent
yang dihasilkan Hilt, Anda perlu membuat antarmuka EntryPoint baru yang dianotasi dengan @InstallIn
. Anotasi InstallIn
memberi tahu Hilt tempat pengambilan binding. Untuk mengakses titik entri, gunakan metode statis yang sesuai dari EntryPointAccessors
. Parameter harus berupa instance komponen atau objek @AndroidEntryPoint
yang berfungsi sebagai pemegang komponen.
RegistrationActivity.kt
class RegistrationActivity : AppCompatActivity() {
@InstallIn(ApplicationComponent::class)
@EntryPoint
interface RegistrationEntryPoint {
fun registrationComponent(): RegistrationComponent.Factory
}
...
}
Sekarang Anda perlu mengganti kode terkait Dagger dengan RegistrationEntryPoint
. Ubah inisialisasi registrationComponent
agar menggunakan RegistrationEntryPoint
. Dengan perubahan ini, RegistrationActivity
dapat mengakses dependensinya melalui kode yang dihasilkan Hilt hingga dimigrasikan untuk menggunakan Hilt.
RegistrationActivity.kt
// Creates an instance of Registration component by grabbing the factory from the app graph
val entryPoint = EntryPointAccessors.fromApplication(applicationContext, RegistrationEntryPoint::class.java)
registrationComponent = entryPoint.registrationComponent().create()
Berikutnya, Anda perlu melakukan pekerjaan dasar yang sama untuk semua jenis Komponen lain yang diekspos. Mari lanjutkan dengan LoginComponent.Factory
. Buka LoginActivity
dan buat antarmuka LoginEntryPoint
yang dianotasi dengan @InstallIn
dan @EntryPoint
seperti yang Anda lakukan sebelumnya, tetapi kali ini memperlihatkan apa yang dibutuhkan LoginActivity
dari komponen Hilt.
LoginActivity.kt
@InstallIn(ApplicationComponent::class)
@EntryPoint
interface LoginEntryPoint {
fun loginComponent(): LoginComponent.Factory
}
Setelah Hilt mengetahui cara menyediakan LoginComponent
, ganti panggilan inject()
lama dengan loginComponent()
EntryPoint.
LoginActivity.kt
val entryPoint = EntryPointAccessors.fromApplication(applicationContext, LoginEntryPoint::class.java)
entryPoint.loginComponent().create().inject(this)
Dua dari tiga jenis AppComponent
yang diekspos diganti agar berfungsi dengan EntryPoint Hilt. Berikutnya, Anda perlu melakukan perubahan yang sama untuk UserManager
. Tidak seperti RegistrationComponent
dan LoginComponent
, UserManager
digunakan di MainActivity
dan SettingsActivity
. Anda hanya perlu membuat antarmuka EntryPoint satu kali. Antarmuka EntryPoint yang dianotasi dapat digunakan di kedua Aktivitas. Agar mudah, deklarasikan Antarmuka di MainActivity.
Untuk membuat antarmuka UserManagerEntryPoint
, buka MainActivity.kt
dan anotasi dengan @InstallIn
dan @EntryPoint
.
MainActivity.kt
@InstallIn(ApplicationComponent::class)
@EntryPoint
interface UserManagerEntryPoint {
fun userManager(): UserManager
}
Sekarang ubah UserManager
agar menggunakan UserManagerEntryPoint
.
MainActivity.kt
val entryPoint = EntryPointAccessors.fromApplication(applicationContext, UserManagerEntryPoint::class.java)
val userManager = entryPoint.userManager()
Anda harus melakukan perubahan yang sama di SettingsActivity.
Buka SettingsActivity.kt
dan ganti cara UserManager
dimasukkan.
SettingsActivity.kt
val entryPoint = EntryPointAccessors.fromApplication(applicationContext, MainActivity.UserManagerEntryPoint::class.java)
val userManager = entryPoint.userManager()
3. Menghapus Factory Komponen
Meneruskan Context
ke komponen Dagger menggunakan @BindsInstance
adalah pola yang umum. Hal ini tidak diperlukan di Hilt karena Context
sudah tersedia sebagai binding standar.
Context
biasanya diperlukan untuk mengakses resource, database, preferensi bersama, dan sebagainya. Hilt menyederhanakan proses injeksi ke konteks dengan menggunakan Penentu @ApplicationContext
dan @ActivityContext
.
Saat memigrasikan aplikasi, periksa jenis yang memerlukan Context
sebagai dependensi dan ganti dengan jenis yang disediakan Hilt.
Dalam kasus ini, SharedPreferencesStorage
memiliki Context
sebagai dependensi. Untuk memberi tahu Hilt agar memasukkan konteks, buka SharedPreferencesStorage.kt. SharedPreferences
memerlukan Context
aplikasi, jadi tambahkan anotasi @ApplicationContext
ke parameter konteks.
SharedPreferencesStorage.kt
class SharedPreferencesStorage @Inject constructor(
@ApplicationContext context: Context
) : Storage {
//...
4. Memigrasikan metode injeksi
Selanjutnya, Anda harus memeriksa kode komponen untuk metode inject()
dan menganotasi class terkait dengan @AndroidEntryPoint
. Dalam kasus kami, AppComponent
tidak memiliki metode inject()
sehingga Anda tidak perlu melakukan apa pun.
5. Menghapus class AppComponent
Karena sudah menambahkan EntryPoints untuk semua komponen yang tercantum di AppComponent.kt
, Anda dapat menghapus AppComponent.kt
.
6. Menghapus kode yang menggunakan Komponen untuk bermigrasi
Anda tidak memerlukan kode untuk melakukan inisialisasi AppComponent
kustom di class aplikasi. Sebagai gantinya, class Aplikasi menggunakan ApplicationComponent yang dihasilkan Hilt. Hapus semua kode di dalam isi class. Kode akhir akan terlihat seperti kode yang tercantum di bawah.
MyApplication.kt
package com.example.android.dagger
import android.app.Application
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
open class MyApplication : Application()
Dengan ini, Anda telah berhasil menambahkan Hilt ke Aplikasi, menghapus AppComponent
, dan mengubah kode Dagger untuk memasukkan dependensi melalui AppComponent yang dihasilkan Hilt. Saat Anda mem-build dan mencoba aplikasi di perangkat atau emulator, aplikasi seharusnya berfungsi seperti biasanya. Di bagian berikut, kita akan memigrasikan setiap Aktivitas dan Fragmen untuk menggunakan Hilt.
Setelah memigrasikan komponen Aplikasi dan menyiapkan dasar-dasarnya, Anda dapat memigrasikan setiap Komponen ke Hilt satu per satu.
Mari mulai memigrasikan alur login. Anda dapat meminta Hilt untuk membuat LoginComponent
dan menggunakannya di LoginActivity
, daripada membuatnya sendiri secara manual.
Anda dapat mengikuti langkah yang sama dengan yang digunakan di bagian sebelumnya, tetapi kali ini menggunakan ActivityComponent
yang dihasilkan Hilt, karena kita akan memigrasikan Komponen yang dikelola oleh Aktivitas.
Untuk memulai, buka LoginComponent.kt. LoginComponent
tidak memiliki modul sehingga Anda tidak perlu melakukan apa pun. Agar Hilt menghasilkan komponen untuk LoginActivity
dan memasukkannya, Anda perlu menganotasi aktivitas dengan @AndroidEntryPoint
.
LoginActivity.kt
@AndroidEntryPoint
class LoginActivity : AppCompatActivity() {
//...
}
Ini adalah kode yang perlu Anda tambahkan untuk memigrasikan LoginActivity
ke Hilt. Karena Hilt akan menghasilkan kode terkait Dagger, Anda hanya perlu melakukan beberapa pembersihan. Hapus antarmuka LoginEntryPoint
.
LoginActivity.kt
//Remove
//@InstallIn(ApplicationComponent::class)
//@EntryPoint
//interface LoginEntryPoint {
// fun loginComponent(): LoginComponent.Factory
//}
Berikutnya, hapus kode EntryPoint di onCreate()
.
LoginActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
//Remove
//val entryPoint = EntryPoints.get(applicationContext, LoginActivity.LoginEntryPoint::class.java)
//entryPoint.loginComponent().create().inject(this)
super.onCreate(savedInstanceState)
...
}
Karena Hilt akan menghasilkan komponen ini, temukan dan hapus LoginComponent.kt.
LoginComponent
saat ini tercantum sebagai subkomponen di AppSubcomponents.kt. Anda dapat menghapus LoginComponent
dari daftar subkomponen dengan aman karena Hilt akan membuat binding untuk Anda.
AppSubcomponents.kt
// This module tells a Component which are its subcomponents
@InstallIn(ApplicationComponent::class)
@Module(
subcomponents = [
RegistrationComponent::class,
UserComponent::class
]
)
class AppSubcomponents
Anda hanya memerlukan ini untuk memigrasikan LoginActivity
agar menggunakan Hilt. Di bagian ini, Anda telah menghapus lebih banyak kode daripada yang ditambahkan. Anda tidak hanya mengetikkan lebih sedikit kode saat menggunakan Hilt, tetapi ini juga berarti lebih sedikit kode untuk memelihara dan memunculkan bug.
Di bagian ini, Anda akan memigrasikan alur pendaftaran. Untuk merencanakan migrasi, mari kita lihat RegistrationComponent
. Buka RegistrationComponent.kt dan scroll ke fungsi inject(). RegistrationComponent
bertanggung jawab untuk memasukkan dependensi ke RegistrationActivity
, EnterDetailsFragment
, dan TermsAndConditionsFragment
.
Mari mulai memigrasikan RegistrationActivity
. Buka RegistrationActivity.kt dan anotasi class dengan @AndroidEntryPoint
.
RegistrationActivity.kt
@AndroidEntryPoint
class RegistrationActivity : AppCompatActivity() {
//...
}
Setelah RegistrationActivity
terdaftar ke Hilt, Anda dapat menghapus Antarmuka RegistrationEntryPoint
dan kode terkait EntryPoint dari fungsi onCreate()
.
RegistrationActivity.kt
//Remove
//@InstallIn(ApplicationComponent::class)
//@EntryPoint
//interface RegistrationEntryPoint {
// fun registrationComponent(): RegistrationComponent.Factory
//}
override fun onCreate(savedInstanceState: Bundle?) {
//Remove
//val entryPoint = EntryPoints.get(applicationContext, RegistrationEntryPoint::class.java)
//registrationComponent = entryPoint.registrationComponent().create()
registrationComponent.inject(this)
super.onCreate(savedInstanceState)
//..
}
Hilt bertanggung jawab untuk menghasilkan komponen dan memasukkan dependensi sehingga Anda dapat menghapus variabel registrationComponent
dan panggilan injeksi pada komponen Dagger yang dihapus.
RegistrationActivity.kt
// Remove
// lateinit var registrationComponent: RegistrationComponent
override fun onCreate(savedInstanceState: Bundle?) {
//Remove
//registrationComponent.inject(this)
super.onCreate(savedInstanceState)
//..
}
Berikutnya, buka EnterDetailsFragment.kt. Anotasi EnterDetailsFragment
dengan @AndroidEntryPoint
, sama seperti yang Anda lakukan di RegistrationActivity
.
EnterDetailsFragment.kt
@AndroidEntryPoint
class EnterDetailsFragment : Fragment() {
//...
}
Karena Hilt menyediakan dependensi, panggilan inject()
pada komponen Dagger yang dihapus tidak diperlukan. Hapus fungsi onAttach()
.
Langkah berikutnya adalah memigrasikan TermsAndConditionsFragment
. Buka TermsAndConditionsFragment.kt, anotasikan class, dan hapus fungsi onAttach()
seperti yang Anda lakukan di langkah sebelumnya. Kode akhir akan terlihat seperti ini.
TermsAndConditionsFragment.kt
@AndroidEntryPoint
class TermsAndConditionsFragment : Fragment() {
@Inject
lateinit var registrationViewModel: RegistrationViewModel
//override fun onAttach(context: Context) {
// super.onAttach(context)
//
// // Grabs the registrationComponent from the Activity and injects this Fragment
// (activity as RegistrationActivity).registrationComponent.inject(this)
//}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_terms_and_conditions, container, false)
view.findViewById<Button>(R.id.next).setOnClickListener {
registrationViewModel.acceptTCs()
(activity as RegistrationActivity).onTermsAndConditionsAccepted()
}
return view
}
}
Dengan perubahan ini, Anda telah memigrasikan semua aktivitas dan fragmen yang tercantum di RegistrationComponent
sehingga Anda dapat menghapus RegistrationComponent.kt.
Setelah menghapus RegistrationComponent
, Anda harus menghapus referensinya dari daftar subkomponen di AppSubcomponents
.
AppSubcomponents.kt
@InstallIn(ApplicationComponent::class)
// This module tells a Component which are its subcomponents
@Module(
subcomponents = [
UserComponent::class
]
)
class AppSubcomponents
Ada satu hal lagi yang perlu dilakukan untuk menyelesaikan migrasi alur Pendaftaran. Alur Pendaftaran mendeklarasikan dan menggunakan cakupannya sendiri, yaitu ActivityScope
. Cakupan mengontrol siklus proses dependensi. Dalam hal ini, ActivityScope
memberi tahu Dagger untuk memasukkan instance RegistrationViewModel
yang sama dalam alur yang dimulai dengan RegistrationActivity
. Hilt menyediakan cakupan siklus proses bawaan untuk mendukung hal ini.
Buka RegistrationViewModel
dan ubah anotasi @ActivityScope
dengan @ActivityScoped
yang disediakan Hilt.
RegistrationViewModel.kt
@ActivityScoped
class RegistrationViewModel @Inject constructor(val userManager: UserManager) {
//...
}
Karena ActivityScope
tidak digunakan di tempat lain, Anda dapat menghapus ActivityScope.kt dengan aman.
Sekarang jalankan aplikasi dan coba alur Pendaftaran. Anda dapat menggunakan nama pengguna dan sandi saat ini untuk login atau membatalkan pendaftaran dan mendaftar ulang dengan akun baru untuk memastikan bahwa alur tersebut berfungsi seperti biasanya.
Saat ini, Dagger dan Hilt berfungsi bersama dalam aplikasi. Hilt memasukkan semua dependensi kecuali untuk UserManager
. Di bagian berikutnya, Anda akan bermigrasi sepenuhnya ke Hilt dari Dagger dengan memigrasikan UserManager
.
Sejauh codelab ini, Anda telah berhasil memigrasikan sebagian besar aplikasi contoh ke Hilt kecuali satu komponen, yaitu UserComponent
. UserComponent
dianotasi dengan cakupan khusus, @LoggedUserScope
. Artinya, UserComponent
akan memasukkan instance UserManager
yang sama ke class yang dianotasi dengan @LoggedUserScope
.
UserComponent
tidak memetakan ke komponen Hilt yang tersedia karena siklus prosesnya tidak dikelola oleh class Android. Karena penambahan komponen kustom di bagian tengah hierarki yang dihasilkan Hilt tidak didukung, Anda memiliki dua opsi:
- Biarkan Hilt dan Dagger bekerja secara berdampingan dalam status project saat ini.
- Migrasikan komponen tercakup ke komponen Hilt yang tersedia dan terdekat (dalam hal ini
ApplicationComponent
) dan gunakan nullability saat diperlukan.
Anda telah mencapai proses #1 di langkah sebelumnya. Pada langkah ini, Anda akan mengikuti proses #2 agar aplikasi sepenuhnya dimigrasikan ke Hilt. Namun, di aplikasi yang sebenarnya, Anda bebas memilih mana yang lebih sesuai untuk kasus penggunaan tertentu.
Pada langkah ini, UserComponent
akan dimigrasikan untuk menjadi bagian dari ApplicationComponent
Hilt. Jika ada modul dalam komponen tersebut, modul tersebut juga harus diinstal di ApplicationComponent
.
Satu-satunya jenis tercakup di UserComponent
adalah UserDataRepository
- yang dianotasi dengan @LoggedUserScope
. Karena UserComponent
akan tergabung dengan ApplicationComponent
Hilt, UserDataRepository
akan dianotasi dengan @Singleton
dan Anda perlu mengubah logika untuk menjadikannya null saat pengguna logout.
UserManager
sudah dianotasi dengan @Singleton
. Artinya, Anda dapat memberikan instance yang sama di seluruh aplikasi dan dengan beberapa perubahan, Anda dapat memperoleh fungsi yang sama dengan Hilt. Mari mulai dengan mengubah cara kerja UserManager
dan UserDataRepository
, karena Anda perlu melakukan dasar-dasarnya terlebih dahulu.
Buka UserManager.kt
dan terapkan perubahan berikut.
- Ganti parameter
UserComponent.Factory
denganUserDataRepository
di konstruktor karena Anda tidak perlu membuat instanceUserComponent
lagi. Parameter tersebut memilikiUserDataRepository
sebagai dependensi - Karena Hilt akan menghasilkan kode komponen, hapus
UserComponent
dan penyetelnya. - Ubah fungsi
isUserLoggedIn()
untuk memeriksa nama pengguna dariuserRepository
, bukan dariuserComponent
. - Tambahkan nama pengguna sebagai parameter ke fungsi
userJustLoggedIn()
. - Ubah isi fungsi
userJustLoggedIn()
untuk memanggilinitData
denganuserName
padauserDataRepository
. Sebagai gantiuserComponent
yang akan Anda hapus selama migrasi. - Tambahkan
username
ke panggilanuserJustLoggedIn()
di fungsiregisterUser()
danloginUser()
. - Hapus
userComponent
dari fungsilogout()
dan ganti dengan panggilan keuserDataRepository.cleanUp()
.
Saat selesai, kode akhir untuk UserManager.kt akan terlihat seperti ini.
UserManager.kt
@Singleton
class UserManager @Inject constructor(
private val storage: Storage,
// Since UserManager will be in charge of managing the UserComponent lifecycle,
// it needs to know how to create instances of it
private val userDataRepository: UserDataRepository
) {
val username: String
get() = storage.getString(REGISTERED_USER)
fun isUserLoggedIn() = userDataRepository.username != null
fun isUserRegistered() = storage.getString(REGISTERED_USER).isNotEmpty()
fun registerUser(username: String, password: String) {
storage.setString(REGISTERED_USER, username)
storage.setString("$username$PASSWORD_SUFFIX", password)
userJustLoggedIn(username)
}
fun loginUser(username: String, password: String): Boolean {
val registeredUser = this.username
if (registeredUser != username) return false
val registeredPassword = storage.getString("$username$PASSWORD_SUFFIX")
if (registeredPassword != password) return false
userJustLoggedIn(username)
return true
}
fun logout() {
userDataRepository.cleanUp()
}
fun unregister() {
val username = storage.getString(REGISTERED_USER)
storage.setString(REGISTERED_USER, "")
storage.setString("$username$PASSWORD_SUFFIX", "")
logout()
}
private fun userJustLoggedIn(username: String) {
// When the user logs in, we create populate data in UserComponent
userDataRepository.initData(username)
}
}
Sekarang, setelah menyelesaikan UserManager
, Anda perlu melakukan beberapa perubahan di UserDataRepository
. Buka UserDataRepository.kt dan terapkan perubahan berikut.
- Hapus
@LoggedUserScope
karena dependensi ini akan dikelola oleh Hilt. UserDataRepository
sudah dimasukkan ke dalamUserManager
. Untuk menghindari dependensi siklus, hapus parameterUserManager
dari konstruktorUserDataRepository
.- Ubah
unreadNotifications
menjadi nullable dan ubah penyetel menjadi pribadi. - Tambahkan variabel nullable baru,
username
, dan ubah penyetel menjadi pribadi. - Tambahkan fungsi baru
initData()
yang menetapkanusername
danunreadNotifications
ke angka acak. - Tambahkan fungsi baru
cleanUp()
untuk mereset jumlahusername
danunreadNotifications
. Tetapkanusername
ke null danunreadNotifications
ke -1. - Terakhir, pindahkan fungsi
randomInt()
ke dalam isi class.
Saat selesai, kode akhir akan terlihat seperti ini.
UserDataRepository.kt
@Singleton
class UserDataRepository @Inject constructor() {
var username: String? = null
private set
var unreadNotifications: Int? = null
private set
init {
unreadNotifications = randomInt()
}
fun refreshUnreadNotifications() {
unreadNotifications = randomInt()
}
fun initData(username: String) {
this.username = username
unreadNotifications = randomInt()
}
fun cleanUp() {
username = null
unreadNotifications = -1
}
private fun randomInt(): Int {
return Random.nextInt(until = 100)
}
}
Untuk menyelesaikan migrasi UserComponent
, buka UserComponent.kt dan scroll ke bawah ke metode inject()
. Dependensi ini digunakan di MainActivity
dan SettingsActivity
. Mari mulai dengan memigrasikan MainActivity
. Buka MainActivity.kt dan anotasikan class tersebut dengan @AndroidEntryPoint
.
MainActivity.kt
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
//...
}
Hapus antarmuka UserManagerEntryPoint
dan kode terkait titik entri dari onCreate()
.
MainActivity.kt
//@InstallIn(ApplicationComponent::class)
//@EntryPoint
//interface UserManagerEntryPoint {
// fun userManager(): UserManager
//}
override fun onCreate(savedInstanceState: Bundle?) {
//val entryPoint = EntryPoints.get(applicationContext, UserManagerEntryPoint::class.java)
//val userManager = entryPoint.userManager()
super.onCreate(savedInstanceState)
//...
}
Deklarasikan lateinit var
untuk UserManager
dan anotasikan dengan anotasi @Inject
agar Hilt dapat memasukkan dependensi.
MainActivity.kt
@Inject
lateinit var userManager: UserManager
Karena UserManager
akan dimasukkan oleh Hilt, hapus panggilan inject()
di UserComponent
.
MainActivity.kt
//Remove
//userManager.userComponent!!.inject(this)
setupViews()
}
}
Ini yang Anda perlukan untuk MainActivity
. Sekarang, Anda dapat melakukan perubahan yang sama untuk memigrasikan SettingsActivity
. Buka SettingsActivity
dan anotasikan dengan @AndroidEntryPoint
.
SettingsActivity.kt
@AndroidEntryPoint
class SettingsActivity : AppCompatActivity() {
//...
}
Buat lateinit var
untuk UserManager
dan anotasikan dengan @Inject
.
SettingsActivity.kt
@Inject
lateinit var userManager: UserManager
Hapus kode titik masuk dan panggilan injeksi pada userComponent()
. Saat selesai, fungsi onCreate()
akan terlihat seperti ini.
SettingsActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_settings)
setupViews()
}
Sekarang Anda dapat membersihkan resource yang tidak digunakan untuk menyelesaikan migrasi. Hapus class LoggedUserScope.kt, UserComponent.kt, dan AppSubcomponent.kt.
Sekarang jalankan dan coba aplikasi lagi. Aplikasi seharusnya berfungsi sama seperti jika digunakan dengan Dagger.
Ada satu langkah penting lagi yang perlu dilakukan sebelum menyelesaikan migrasi aplikasi ke Hilt. Sejauh ini Anda telah memigrasikan semua kode aplikasi, tetapi tidak dengan pengujiannya. Hilt memasukkan dependensi dalam pengujian seperti yang dilakukan di kode aplikasi. Pengujian dengan Hilt tidak memerlukan pemeliharaan karena Hilt otomatis menghasilkan serangkaian komponen baru untuk setiap pengujian.
Pengujian unit
Mari kita mulai dengan Pengujian unit. Anda tidak perlu menggunakan Hilt untuk pengujian unit karena Anda bisa langsung memanggil konstruktor class target yang meneruskan dependensi palsu atau tiruan seperti jika konstruktor tidak dianotasi.
Jika menjalankan pengujian unit, Anda akan melihat UserManagerTest gagal. Anda telah melakukan banyak proses dan perubahan di UserManager, termasuk parameter konstruktornya di bagian sebelumnya. Buka UserManagerTest.kt yang masih bergantung pada UserComponent
dan UserComponentFactory
. Karena Anda sudah mengubah parameter UserManager
, ubah parameter UserComponent.Factory
dengan instance UserDataRepository
baru.
UserManagerTest.kt
@Before
fun setup() {
storage = FakeStorage()
userManager = UserManager(storage, UserDataRepository())
}
Selesai. Jalankan pengujian lagi dan semua pengujian unit seharusnya berhasil.
Menambahkan Dependensi Pengujian
Sebelum melanjutkan, buka app/build.gradle
dan pastikan dependensi Hilt berikut ada. Hilt menggunakan hilt-android-testing
untuk anotasi khusus pengujian. Selain itu, karena Hilt perlu menghasilkan kode untuk class di folder androidTest
, pemroses anotasinya juga harus dapat dijalankan di sana.
app/build.gradle
// Hilt testing dependencies
androidTestImplementation "com.google.dagger:hilt-android-testing:$hilt_version"
kaptAndroidTest "com.google.dagger:hilt-android-compiler:$hilt_version"
Pengujian UI
Hilt otomatis menghasilkan komponen pengujian dan Aplikasi pengujian untuk setiap pengujian. Untuk memulai, buka TestAppComponent.kt untuk merencanakan migrasi. TestAppComponent
memiliki 2 modul, yaitu TestStorageModule
dan AppSubcomponents
. Anda telah memigrasikan dan menghapus AppSubcomponents
, jadi Anda dapat melanjutkan proses migrasi TestStorageModule
.
Buka TestStorageModule.kt dan anotasikan class dengan anotasi @InstallIn
.
TestStorageModule.kt
@InstallIn(ApplicationComponent::class)
@Module
abstract class TestStorageModule {
//...
Karena Anda telah selesai memigrasikan semua modul, lanjutkan dan hapus TestAppComponent
.
Selanjutnya, tambahkan Hilt ke ApplicationTest
. Anda harus menganotasi setiap pengujian UI yang menggunakan Hilt dengan @HiltAndroidTest
. Anotasi ini bertanggung jawab untuk menghasilkan komponen Hilt untuk setiap pengujian.
Buka ApplicationTest.kt dan tambahkan anotasi berikut:
@HiltAndroidTest
untuk memberi tahu Hilt agar menghasilkan komponen untuk pengujian ini.@UninstallModules(StorageModule::class)
untuk memberi tahu Hilt agar meng-uninstalStorageModule
yang dideklarasikan dalam kode aplikasi sehinggaTestStorageModule
akan dimasukkan selama pengujian.- Anda juga perlu menambahkan
HiltAndroidRule
keApplicationTest
. Aturan pengujian ini mengelola status komponen dan digunakan untuk melakukan injeksi pada pengujian Anda. Kode akhir akan terlihat seperti ini.
ApplicationTest.kt
@UninstallModules(StorageModule::class)
@HiltAndroidTest
class ApplicationTest {
@get:Rule
var hiltRule = HiltAndroidRule(this)
//...
Karena Hilt menghasilkan Application
baru untuk setiap uji instrumentasi, kita perlu menentukan bahwa Application
yang dihasilkan Hilt harus digunakan saat menjalankan pengujian UI. Untuk melakukannya, kita memerlukan runner pengujian kustom.
Aplikasi codelab sudah memiliki runner pengujian kustom. Buka MyCustomTestRunner.kt
Hilt sudah dilengkapi dengan Application
yang dapat Anda gunakan untuk pengujian, yang bernama HiltTestApplication.
Anda perlu mengubah MyTestApplication::class.java
dengan HiltTestApplication::class.java
di isi fungsi newApplication()
.
MyCustomTestRunner.kt
class MyCustomTestRunner : AndroidJUnitRunner() {
override fun newApplication(cl: ClassLoader?, name: String?, context: Context?): Application {
return super.newApplication(cl, HiltTestApplication::class.java.name, context)
}
}
Dengan perubahan ini, sekarang Anda dapat menghapus file MyTestApplication.kt dengan aman. Sekarang lanjutkan dan jalankan pengujian. Semua pengujian seharusnya berhasil.
Hilt menyertakan ekstensi untuk menyediakan class dari library Jetpack lainnya, seperti WorkManager dan ViewModel. ViewModels dalam project codelab adalah class biasa yang tidak memperluas ViewModel
dari Komponen Arsitektur. Sebelum menambahkan dukungan Hilt untuk ViewModels, migrasikan ViewModels di aplikasi ke ViewModels Komponen Arsitektur.
Untuk mengintegrasikan dengan ViewModel
, Anda perlu menambahkan dependensi tambahan berikut ke file gradle. Dependensi ini sudah ditambahkan untuk Anda. Perlu diingat bahwa selain library, Anda perlu menambahkan pemroses anotasi tambahan yang berfungsi mendukung pemroses anotasi Hilt:
// app/build.gradle file
...
dependencies {
...
implementation "androidx.fragment:fragment-ktx:1.2.4"
implementation 'androidx.hilt:hilt-lifecycle-viewmodel:$hilt_jetpack_version'
kapt 'androidx.hilt:hilt-compiler:$hilt_jetpack_version'
kaptAndroidTest 'androidx.hilt:hilt-compiler:$hilt_jetpack_version'
}
Untuk memigrasikan class biasa ke ViewModel
, Anda perlu memperluas ViewModel()
.
Buka MainViewModel.kt dan tambahkan : ViewModel()
. Proses ini cukup untuk memigrasikan ViewModels Komponen Arsitektur, tetapi Anda juga perlu memberi tahu Hilt cara memberikan instance ViewModel. Untuk melakukannya, tambahkan anotasi @ViewModelInject
di konstruktor ViewModel
. Ganti anotasi @Inject
dengan @ViewModelInject
.
MainViewModel.kt
class MainViewModel @ViewModelInject constructor(
private val userDataRepository: UserDataRepository
): ViewModel() {
//...
}
Selanjutnya, buka LoginViewModel
dan lakukan perubahan yang sama. Kode akhir akan terlihat seperti ini.
LoginViewModel.kt
class LoginViewModel @ViewModelInject constructor(
private val userManager: UserManager
): ViewModel() {
//...
}
Demikian pula, buka RegistrationViewModel.kt lalu migrasikan ke ViewModel()
dan tambahkan anotasi Hilt. Anda tidak memerlukan anotasi @ActivityScoped
karena dengan metode ekstensi viewModels()
dan activityViewModels()
, Anda dapat mengontrol cakupan ViewModel
ini.
RegistrationViewModel.kt
class RegistrationViewModel @ViewModelInject constructor(
val userManager: UserManager
) : ViewModel() {
Lakukan perubahan yang sama untuk memigrasikan EnterDetailsViewModel
dan SettingViewModel
. Kode akhir untuk kedua class ini akan terlihat seperti ini.
EnterDetailsViewModel.kt
class EnterDetailsViewModel @ViewModelInject constructor() : ViewModel() {
SettingViewModel.kt
class SettingsViewModel @ViewModelInject constructor(
private val userDataRepository: UserDataRepository,
private val userManager: UserManager
) : ViewModel() {
Setelah semua ViewModels dimigrasikan ke Viewmodels Komponen Arsitektur dan dianotasi dengan anotasi Hilt, Anda dapat memigrasikan cara injeksi.
Selanjutnya, Anda perlu mengubah cara ViewModels diinisialisasi di lapisan View. ViewModels dibuat oleh OS dan cara mendapatkannya adalah dengan menggunakan fungsi delegasi by viewModels()
.
Buka MainActivity.kt, ganti anotasi @Inject
dengan ekstensi Jetpack. Perlu diingat bahwa Anda juga harus menghapus lateinit
, mengubah var
menjadi val
, dan menandai kolom private
.
MainActivity.kt
// @Inject
// lateinit var mainViewModel: MainViewModel
private val mainViewModel: MainViewModel by viewModels()
Demikian pula, buka LoginActivity.kt dan ubah cara mendapatkan ViewModel
.
LoginActivity.kt
// @Inject
// lateinit var loginViewModel: LoginViewModel
private val loginViewModel: LoginViewModel by viewModels()
Selanjutnya, buka RegistrationActivity.kt dan terapkan perubahan serupa untuk mendapatkan registrationViewModel
.
RegistrationActivity.kt
// @Inject
// lateinit var registrationViewModel: RegistrationViewModel
private val registrationViewModel: RegistrationViewModel by viewModels()
Buka EnterDetailsFragment.kt. Ganti cara mendapatkan EnterDetailsViewModel
.
EnterDetailsFragment.kt
private val enterDetailsViewModel: EnterDetailsViewModel by viewModels()
Demikian pula, ganti cara mendapatkan registrationViewModel
, tetapi kali ini gunakan fungsi delegasi activityViewModels()
, bukan viewModels().
. Saat registrationViewModel
dimasukkan, Hilt akan memasukkan ViewModel cakupan tingkat aktivitas.
EnterDetailsFragment.kt
private val registrationViewModel: RegistrationViewModel by activityViewModels()
Buka TermsAndConditionsFragment.kt dan sekali lagi gunakan fungsi ekstensi activityViewModels()
, bukan viewModels()
, untuk mendapatkan registrationViewModel.
TermsAndConditionsFragment.kt
private val registrationViewModel: RegistrationViewModel by activityViewModels()
Terakhir, buka SettingsActivity.kt dan migrasikan cara settingsViewModel
diperoleh.
SettingsActivity.kt
private val settingsViewModel: SettingsViewModel by viewModels()
Sekarang, jalankan aplikasi dan pastikan aplikasi berfungsi seperti yang diharapkan.
Selamat! Anda berhasil memigrasikan aplikasi untuk menggunakan Hilt. Tidak hanya menyelesaikan migrasi, Anda juga berhasil menjaga aplikasi tetap berfungsi saat memigrasikan komponen Dagger satu per satu.
Dalam codelab ini, Anda telah mempelajari cara memulai komponen Aplikasi dan membuat dasar yang diperlukan agar Hilt berfungsi dengan komponen Dagger yang ada. Dari situ, Anda telah memigrasikan setiap Komponen Dagger ke Hilt dengan menggunakan Anotasi Hilt pada Aktivitas dan Fragmen dan menghapus kode terkait Dagger. Setiap kali Anda menyelesaikan migrasi suatu komponen, aplikasi bekerja dan berfungsi seperti yang diharapkan. Anda juga memigrasikan dependensi Context
dan ApplicationContext
dengan anotasi @ActivityContext
dan @ApplicationContext
yang disediakan Hilt. Anda telah memigrasikan komponen Android lainnya. Terakhir, Anda berhasil memigrasikan pengujian dan menyelesaikan migrasi ke Hilt.
Bacaan lebih lanjut
Untuk mempelajari cara memigrasikan aplikasi ke Hilt lebih lanjut, lihat dokumentasi Migrasi ke Hilt. Selain informasi lebih lanjut tentang migrasi Dagger ke Hilt, Anda juga dapat melihat informasi tentang migrasi aplikasi dagger.android.