The environment
AndroidStudio (2) kotlin (kotlin_version = “1.3.71”)
Note: compile exception when kotlin_version = “1.5.0”.
New member of Jetpack Hilt
Dependency Injection (DI). It’s usually used to decouple. Square launched the Dagger framework in 2012, which is based on Java reflection (time-consuming and difficult to use). Google forked the Dagger code and changed it to Dagger2 based on annotations to solve the reflection problem. Overdesign some simple projects. Hilt borrows from Dagger2 to make it simple and provides An Android-specific API.
The introduction of Hilt
In the project and directory build.gradle configuration:
buildscript {
dependencies {
//
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com. Google. Dagger hilt - android - gradle - plugin: 2.28 alpha'}}Copy the code
In app/build.gradle:
plugins {
id 'kotlin-android'
id 'kotlin-kapt'
id 'dagger.hilt.android.plugin'
}
dependencies {
implementation Com. Google. "dagger hilt - android: 2.28 alpha." "
kapt Com. Google. "dagger hilt - android - the compiler: 2.28 alpha." "
}
Copy the code
With Hilt, you must customize an Application or Hilt will not work. Customize MyApplication:
@HiltAndroidApp
class MyApplication :Application(){
...
}
Copy the code
Then register MyApplication in the manifest file androidmanifest.xml. Hilt greatly simplifies the use of Dagger2 by not using the @Component annotation to write bridge layer logic; Limited to a few fixed Android portals to start with. Hilt supports a total of 6 entries:
- Application
- Activity
- Fragment
- View
- Service
- BroadcastReceiver
The Application entry point is declared using the @hiltAndroidApp annotation. All other entry points are declared using the @AndroidEntryPoint annotation. Activity entry point:
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {... }Copy the code
Inject:
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
@Inject
lateinit var truck: Truck
override fun onCreate(savedInstanceState: Bundle?). {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
truck.deliver()
}
}
Copy the code
Lateinit is a delayed kotlin syntax. Inject the @inject annotation into the Truck member variable. Note that fields injected by Hilt cannot be declared private. Truck categories:
class Truck @Inject constructor() {fun deliver(a){
println("run deliver")}}Copy the code
The @inject annotation tells Hilt to create by construct.
Parameterized dependency injection
The MainActivity above remains unchanged. Truck categories:
class Truck @Inject constructor(val driver: Driver){
fun deliver(a){
println("run deliver by:$driver")}}class Driver @Inject constructor() {}Copy the code
Truck adds a Driver parameter by construction; Declare an @Inject annotation on the Driver class constructor. Truck can only be dependency injected if all other objects that depend on the constructor for Truck support dependency injection.
Dependency injection of interfaces
Engine interface:
interface Engine {
fun start(a)
fun shutdown(a)
}
Copy the code
Implementation classes: dependency injection, GasEngine and ElectricEngine
class GasEngine @Inject constructor() : Engine {
override fun start(a) {
println("Gas start")}override fun shutdown(a) {
println("Gas shutdown")}}class ElectricEngine @Inject constructor() : Engine {
override fun start(a) {
println("Electric start")}override fun shutdown(a) {
println("Electric shutdown")}}Copy the code
New abstract class implementation bridge: EngineModule
@Module
@InstallIn(ActivityComponent::class)
abstract class EngineModule {
@Binds
abstract fun bindEngine(gasEngine: GasEngine): Engine
}
Copy the code
The @module annotation provides the dependency injection instance Module. Provide the instances required by the Engine interface. The return value of an abstract function must be Engine, which provides an instance for an interface of Engine type. An abstract function is provided with an instance of whatever argument it receives. If you add @bind to an abstract function, it’s Hilt. When changing a vehicle’s engine, you just need to change the parameters of the abstract method bindEngnie in the EngineModule.
Inject different instances of the same type
Qualifier annotations to inject different instances of the same type of class or interface. Add two custom annotations:
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class BindGasEngine
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class BindElectricEngine
Copy the code
@ Retention used to declare annotation scope, choose AnnotationRetention. BINARY annotation will be saved after the compilation, but can’t through reflection to access the annotation. Class EngineModule modified:
@Module
@InstallIn(ActivityComponent::class)
abstract class EngineModule {
@BindGasEngine
@Binds
abstract fun bindGasEngine(gasEngine: ElectricEngine): Engine
@BindElectricEngine
@Binds
abstract fun bindElectricEngine(electricEngine: ElectricEngine): Engine
}
Copy the code
Dependency injection changes for all Engine types:
class Truck @Inject constructor(val driver: Driver) {
@BindGasEngine
@Inject
lateinit var gasEngine: Engine
@BindElectricEngine
@Inject
lateinit var electricEngine: Engine
fun deliver(a) {
gasEngine.start()
electricEngine.start()
println("run deliver by:$driver")
gasEngine.shutdown()
electricEngine.shutdown()
}
}
Copy the code
Solved the problem of injecting different instances of the same type.
Dependency injection of third-party classes
For example, okhttp adds dependencies to your project’s app/build.gradle:
implementation "Com. Squareup. Okhttp3: okhttp: 3.11.0"
Copy the code
Define the class NetworkModule:
@Module
@InstallIn(ActivityComponent::class)
class NetworkModule {
@Provides
fun provideOkHttpClient(a): OkHttpClient {
return OkHttpClient.Builder()
.connectTimeout(20, TimeUnit.SECONDS)
.readTimeout(20, TimeUnit.SECONDS)
.writeTimeout(20, TimeUnit.SECONDS)
.build()
}
}
Copy the code
Note that this is not an abstract class, because you need to implement third-party class initialization, not an abstract function. Concrete implementation OkHttpClient creation. The function name is custom and the return value must be OkHttpClient. The @providers annotation is added to the provideOkHttpClient function so that Hilt can identify it. In use:
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
@Inject
lateinit var okHttpClient: OkHttpClient
override fun onCreate(savedInstanceState: Bundle?). {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
println("okhttp-user:$okHttpClient")}}Copy the code
Third party dependencies are addressed, but fewer and fewer people are using Okhttp, and more are using Retrofit as a network request library, which Retrofit actually encapsulates Okhttp. We want a Retrofit type in NetworkModule:
@Module
@InstallIn(ActivityComponent::class)
class NetworkModule {
@Provides
fun provideOkHttpClient(a): OkHttpClient {
return OkHttpClient.Builder()
.connectTimeout(20, TimeUnit.SECONDS)
.readTimeout(20, TimeUnit.SECONDS)
.writeTimeout(20, TimeUnit.SECONDS)
.build()
}
@Provides
fun providerRetrofit(okHttpClient: OkHttpClient):Retrofit{
return Retrofit.Builder()
.baseUrl("http://baidu.com") .client(okHttpClient) .build(); }}Copy the code
Define a provderRetrofit function to create a Retrofit instance return. ProviderRetrofit receives an OkHttpClient parameter, which does not need to be passed. Because Hilt will find the OkHttpClient implementation of provideOkHttpClient itself. In use:
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
@Inject
lateinit var retrofit: Retrofit
override fun onCreate(savedInstanceState: Bundle?). {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
println("retrofit-user:$retrofit")}}Copy the code
Hilt built-in components and component scopes
@installin (ActivityComponent::class) installs this module into the Activity component. The Activity can then use all of the dependency injection instances provided by this module. Fragments and views contained in an Activity can also be used, but not anywhere else except for activities, fragments, and Views. For example, an error is reported when the Service uses @Inject to Inject a Retrofit field.
Hilt comes with seven built-in component types:
- ApplicationComponent: Application
- ActivityRetainedComponent: ViewModel
- ActivityComponent: Activity
- FragmentComponent: Fragment
- ViewComponent: View
- ViewWithFragmentComponent: View annotated with @WithFragmentBindings
- ServiceComponent: Service
Each component has a different scope. The dependency injection provided by ApplicationComponent can actually be used throughout the project. If you want the Retrofit instances provided by NetworkModule to be dependent on services, you can:
@Module
@InstallIn(ApplicationComponent::class)
class NetworkModule {}Copy the code
Hilt scope, where a different instance is created for each dependency injection behavior. Unreasonable in some cases. In some cases, only one instance is used globally, and it is not reasonable to create each instance. This can be handled by @singleton.
@Module
@InstallIn(ApplicationComponent::class)
class NetworkModule {
@Singleton
@Provides
fun provideOkHttpClient(a): OkHttpClient {
return OkHttpClient.Builder()
.connectTimeout(20, TimeUnit.SECONDS)
.readTimeout(20, TimeUnit.SECONDS)
.writeTimeout(20, TimeUnit.SECONDS)
.build()
}
@Singleton
@Provides
fun providerRetrofit(okHttpClient: OkHttpClient):Retrofit{
return Retrofit.Builder()
.baseUrl("http://baidu.com") .client(okHttpClient) .build(); }}Copy the code
It is guaranteed that only one instance of OkhttpClient and Retrofit will exist globally. Class — component — scope
If you want to share an object instance across the program, use @Singleton. To use an object instance of an Activity with fragments and views inside it, use @activityScoped. You don’t have to use a scope annotation in a Module. You can declare it directly above any class, for example:
@Singleton
class Driver @Inject constructor() {}
Copy the code
Drivers share an instance globally across the entire project, and they can be dependency injected globally. If changed to @activityScoped, the Driver will share an instance within the same Activity, and activities, fragments, and Views can all inject dependencies into the Driver class. Inclusion relation:
Preset the Qualifier
If the class Driver needs Context:
@Singleton
class Driver @Inject constructor(val context:Context) {}
Copy the code
You get an error because you don’t know who supplied the Context. Echo Dirver’s method referenced by Truck, annotating the constructor with @inject, is inoperable because it does not have permissions to write the Context class. Use NetworkModule @Module to provide dependency injection to the Context as a third party class:
@Module
@InstallIn(ApplicationComponent::class)
class ContextModule {
@Provides
fun provideContext(a): Context { ??? }}Copy the code
But how do you do that, because you can’t just new a Context instance. Context is a system component, and instances are created by the Android system, so some of the previous methods cannot be implemented. You need to provide a dependency injection instance of Context through @qualifier. Add @ ApplicationContext
@Singleton
class Driver @Inject constructor(@ApplicationContext val context:Context) {}
Copy the code
Hilt will automatically provide an application-type Context to the Truck class, which can then use the Context to write business logic. If you need an ActivityContext instead of an Application Context, Hilt is another Qualifier, @activityContext
@ActivityScoped
class Driver @Inject constructor(@ActivityContext val context:Context) {}
Copy the code
I used @activityScoped above because @Singleton compiler reported an error (because it was global). You can also use @fragmentScoped, @viewscoped, or just delete it so that no errors are reported. Qualifier Hilt presets injection capabilities for Application and Activity types. If you rely on an Application or Activity and do not need to provide an instance of dependency injection, Hilt automatically recognizes:
class Driver @Inject constructor(val application:Application) {}
class Driver @Inject constructor(val activity:Activity) {}
Copy the code
Compilation can be done without adding any annotation declarations. Must be Application and Activity types, whose subtypes will not pass compilation. If the argument is MyApplication:
@Module
@InstallIn(ApplicationComponent::class)
class ApplicationModule {
@Provides
fun provierMyApplication(application: Application): MyApplication {
return application as MyApplication
}
}
class Driver @Inject constructor(val application: MyApplication) {}
Copy the code
The provierMyApplication function receives an Application parameter, which Hilt automatically recognizes, and then converts it down to MyApplication. You can declare dependencies in the Truck class.
ViewModel dependency injection
In the MVVM architecture, it is appropriate for Hilt to manage the repository layer instance creation.
The first mode:
Warehouse layer Repository:
class Repository @Inject constructor() {}Copy the code
Repository relies on injection into the ViewModel, so annotate the constructor with @inject. MyViewModel inherits from ViewModel for the ViewModel layer:
@ActivityRetainedScoped
class MyViewModel @Inject constructor(val repository: Repository):ViewModel(){}
Copy the code
- MyViewModel is declared as an @ActivityRetainedScoped annotation, which is provided specifically for the ViewModel and has the same lifecycle as the ViewModel.
- Declare @Inject annotation in the constructor of MyViewModel because the Activity uses dependency injection to get the MyViewModel instance.
- The MyViewModel constructor takes the Repository argument to indicate that MyViewModel is dependent on Repository.
Get an instance of MyViewModel in MainActivity via dependency injection:
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
@Inject
lateinit var viewModel: MyViewModel
override fun onCreate(savedInstanceState: Bundle?). {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
println("viewModel-user:$viewModel")}}Copy the code
There are drawbacks: MyViewModel is also dependency injected into Repository, which is intended only for Repository dependency injection.
The second:
We don’t want the ViewModel to follow dependency injection. Hilt specifically provides a standalone dependency method, adding dependencies to app/build.gradle:
implementation 'androidx. Hilt: hilt - lifecycle - viewmodel: 1.0.0 - alpha02'
kapt 'androidx. Hilt: hilt - compiler: 1.0.0 - alpha02'
Copy the code
Modify MyViewModel code:
class MyViewModel @ViewModelInject constructor(val repository: Repository):ViewModel(){}
Copy the code
The @activityRetainedScoped annotation is removed, it is not needed. The @Inject annotation is changed to the @ViewModelInject annotation, which is specifically used by ViewModel. In the MainActivity:
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?). {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val viewModel:MyViewModel by lazy {
ViewModelProvider(this).get(MyViewModel::class.java)
}
println("viewModel-user:$viewModel")}}Copy the code
In this way, although we don’t use dependency injection in MainActivity, the @AndroidEntryPoint annotation is essential. Otherwise, at compile time, Hilt cannot detect syntax exceptions, and at run time, Hilt cannot find entry points and cannot perform dependency injection.
Unsupported entry point
One of the components supported by Hilt is ContentProvider(one of the four components). The ContentProvider lifecycle is a special one, executed before the onCreate method of the Application, and initialized by some third-party libraries (Jetpack member App Startup). The principle of Hilt starts with the onCreate method of the Application, and all Hilt functions are not working properly until this method is executed. Hilt does not include ContentProvider as a supported entry point. You can rely on injection functionality in other ways.
class MyContentProvider :ContentProvider() {@EntryPoint
@InstallIn(ApplicationComponent::class)
interface MyEntryPoint{
fun getRetrofit(a):Retrofit
}
override fun onCreate(a): Boolean {
println("provider==onCreate") context? .let {val appContext = it.applicationContext
val entryPoint = EntryPointAccessors.fromApplication(appContext,MyEntryPoint::class.java)
val retrofit = entryPoint.getRetrofit()
println("ContentProvider==retrofit:$retrofit")}return false}}Copy the code
Define the MyEntryPoint interface, using @EntryPoint to declare custom entry points and @installin to declare scopes. The getRetrofit function is defined in MyEntryPoint and the return type is Retrofit. Injection is written for Retrofit, and initialization is done in NetworkModule. With the EntryPointAccessors class, call the fromApplication function to get the custom entry point instance.