Analysis of Hilt Dependency Injection
What is Dependency Injection?
First of all, a member variable of a certain class is called dependency. If this variable wants to instantiate a method that refers to its class, it can pass parameters through the constructor or obtain the object through a certain method. Such methods of obtaining object instances through external methods are called Dependency injection; and dependency injection can be simply divided 手动注入
into 自动注入
two methods; it is a dependency injection library based on Hilt
Dagger , Hilt is a dependency injection library specially created by Google for the Android platform, which is greatly simplified in use ( 场景化优化
compared to dagger)
Benefits of using dependency injection
The advantages described in the Google docs are as follows:
- reuse code
- easy to refactor
- easy to test
Simply put, some content needs to be manually called layer by layer during development, and it needs to be instantiated manually; for Hilt, it can automatically perform dependency injection, and provide a container for each Android class in the project and automatically manage it Its life cycle; some data that needs to be used globally can be shared through Hilt, thereby simplifying the code
Commonly used predefined qualifiers in Hilt
@HiltAndroidApp
All applications using Hilt must contain an @HiltAndroidApp
annotated Application class. @HiltAndroidApp
It will trigger Hilt's code generation operation. The generated code includes a base class of the application, which acts as an application-level dependency container. Remember that this Application class must be referenced in the manifest file
@HiltAndroidApp
class App : Application() {
... }
@AndroidEntryPoint
If an Activity wants to use Hilt dependency injection, it must be added to the Activity @AndroidEntryPoint注解
. Similarly, a Fragment in the Activity also needs to be used and must be added @AndroidEntryPoint注解
;
currently Hilt supports the following Android classes:
-
Application (by using @HiltAndroidApp)
-
ViewModel (by using @HiltViewModel)
-
Activity
-
Fragment
-
View
-
Service
-
BroadcastReceiver
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
...}
@Module
Hilt has a module class, using @Module
the annotation class, it will tell Hilt which instances it provides, usually @Provider
used in conjunction with it, you must @InstallIn
add scope to it through annotations
@InstallIn
For each Android class from which you can perform field injection, there is an associated Hilt component that you can reference in @InstallIn
annotations . Each Hilt component is responsible for injecting its bindings into the corresponding Android class.
Android components | default binding |
---|---|
SingletonComponent | Application |
ActivityRetainedComponent | Application |
ViewModelComponent | SavedStateHandle |
ActivityComponent | Application 和 Activity |
FragmentComponent | Application、Activity 和 Fragment |
ViewComponent | Application、Activity 和 View |
ViewWithFragmentComponent | Application、Activity、Fragment 和 View |
ServiceComponent | Application and Service |
@Module
@InstallIn(SingletonComponent::class)
object AppModule {
...}
@Provides
If you want to let Hilt know which instance objects are provided, you can use them @Provides
to make annotations. A database instance is provided below, and the scope is a global singleton
@Provides
@Singleton
fun providerAccountBean():AccountBean{
return AccountBean("FranzLiszt","123456")
}
@Inject
If you want to use Hilt to automatically implement dependency injection objects, you can use @Inject
annotations to call them; as follows, without instantiation, it will automatically refer to the providerAccountBean()
provided objects
@Inject lateinit var bean: AccountBean
@HiltViewModel
It can be understood from the name that it needs to ViewModel
be used in conjunction with the class to act on the ViewModel; as follows, @Inject
it is used in conjunction with the constructor to reference Hilt
the object provided above
@HiltViewModel
class AccountViewModel @Inject constructor(private val bean: AccountBean):ViewModel() {
...}
The use of Hilt
rely
Step 1: project/build.gradle
Add the following code in
plugins {
...
id 'com.google.dagger.hilt.android' version '2.44' apply false
}
Step 2: app/build.gradle
Add the following code in
plugins {
...
id 'kotlin-kapt'
id 'dagger.hilt.android.plugin'
}
Step 3: app/build.gradle
Add the following code in
//Dagger - Hilt
implementation("com.google.dagger:hilt-android:2.44")
kapt("com.google.dagger:hilt-android-compiler:2.44")
// Compose dependencies
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.4.0-beta01"
implementation "androidx.hilt:hilt-navigation-compose:1.0.0-alpha03"
Step 4: app/build.gradle
Add the following code in
android{
...
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
create entity class
data class AccountBean(var username:String,var password:String)
Add Hilt entry
@HiltAndroidApp
class APP:Application() {
}
provide object
As a use case here, just simply write a test case, you can write a complex program in the function, and it will not affect the performance of the program. It will take a little time when the program is initialized and compiled. Provide a singleton AccountBean
object
@Module
@InstallIn(SingletonComponent::class)
object AppModule {
@Provides
@Singleton
fun providerAccountBean():AccountBean{
return AccountBean("FranzLiszt","123456")
}
}
get object
AndroidEntryPoint
Add the Hilt annotation to the Activity , and then you can get the object provided by Inject
the above method through the annotationproviderAccountBean
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
@Inject lateinit var bean: AccountBean
...
}
Then you can directly refer to the object
@Composable
fun showAccountInfo() {
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = bean.username)
Spacer(modifier = Modifier.height(20.dp))
Text(text = bean.password)
}
}
Application with ViewModel
Use annotations in ViewModel HiltViewModel
, and then you can use Inject
annotations to get Hilt to automatically inject dependent objects
@HiltViewModel
class AccountViewModel @Inject constructor(private val bean: AccountBean):ViewModel() {
...}
Define two member variables with state, which AccountState
is a data class with two member variables text and hint
private val _usernameState = mutableStateOf(AccountState(
hint = "input username"
))
val usernameState:State<AccountState> = _usernameState
private val _passwordState = mutableStateOf(AccountState(
hint = "input password"
))
val passwordState:State<AccountState> = _passwordState
In the ViewModel initialization function, assign the value of the object provided by Hilt to the two state variables in the VM
init {
_usernameState.value = usernameState.value.copy(text = bean.username)
_passwordState.value = passwordState.value.copy(text = bean.password)
}
Corresponding processing is performed through external events, and the value of the external input box is continuously modified. Here, it is synchronized to the state variable in the VM.
fun onEvent(event: AccountEvent){
when(event){
is AccountEvent.ChangeUsername ->{
_usernameState.value = usernameState.value.copy(text = event.value)
}
is AccountEvent.ChangePassword ->{
_passwordState.value = passwordState.value.copy(text = event.value)
}
}
}
use
Here, directly obtain the values of the two member variables of the ViewModel, and then bind them to the two input boxes. By listening to the method of the input box onValueChange
, when the value is continuously modified, then call the method in the VM onEvent
, and then modify the state content in the VM , since the input box is bound to the state variable of the VM, and after the value of the state variable changes, the corresponding UI interface will重组
@Composable
fun showAccountInfo (viewModel: AccountViewModel = hiltViewModel()){
val username = viewModel.usernameState.value
val password = viewModel.passwordState.value
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
TextField(
value = username.text,
placeholder = {
Text(text = username.hint)},
onValueChange = {
viewModel.onEvent(AccountEvent.ChangeUsername(it))
}
)
Spacer(modifier = Modifier.height(20.dp))
TextField(
value = password.text,
placeholder = {
Text(text = password.hint)},
onValueChange = {
viewModel.onEvent(AccountEvent.ChangePassword(it))
}
)
}
}
Summarize
The above two test cases are just the tip of the iceberg of the convenience provided by Hilt; Hilt is very convenient to use, and it can improve the code structure and save unnecessary code; the dependency Koin
injection library is also well received, although I have not used it. However, because of Google's push Hilt
, I started first; in terms of performance, I read some other articles, and the difference is not bad, so I can choose according to my own needs.