Original: EnglishJetpack architecture components to learn (5) - Hilt injection framework to use - Stars-One's grocery nest
This requires basic knowledge of Kotlin, otherwise you may have trouble reading this!
Introductory note
In fact, Guo Lin's article has already made it clearer (specific reference links are posted below), here is a brief summary.
If in accordance with our previous MVC writing , we can directly in the activity to initiate network requests , but to initiate network requests we need to call a specific method of the Api object , and the Api object can only be created in activity
Here activity and api objects are actually coupled, objectively speaking, we areactivity should not be responsible for creating an api object.
So using the injection framework, it is equivalent to have a middleman to help activity processing.As to whether the intermediary finds the api object directly, or the intermediary creates the api objectI don't even care about the activity.activity can help get an api object just by knowing to go to an intermediary
vantage
Advantages felt so far after the study.
- Maybe it's better for large projects with multiple modules.
- MMVM/MVI architecture apps are also better suited for
- Injection of interfaces to facilitate the implementation of different logic
may be the interface injection may be of some use, such as the above example, assuming that our network framework at the beginning of the okhttp, but later may change to other frameworks, we can consider encapsulating a common interface, and then use the dependency injection, the later replacement of other network frameworks only need to implement the interface of the corresponding method can be
Dependency injection is more targeted at MVVM/MVI architecture app, traditional MVC structure, I directly a single instance object, you can solve the problem, as if there is no need?
Most of what is said on the Internet is decoupled, easy to follow the test, the problem is that I do not write test cases, really can not feel the specific benefits are
In short, the current study of this just because many open source projects are beginning to use, learn this found that probably to understand haha, but also by the way to do the next record!
Basic use
1. Dependency introduction
Note: I'm using the ksl gradle script below.
Documentation for the project
buildscript {
...
dependencies {
...
classpath ':hilt-android-gradle-plugin:2.48.1'
}
}
Add plugins and dependencies to the app module
plugins {
id("")
id("kotlin-kapt") // kotlin-kapt plug-in (software component)
id("") // Hilt plug-in (software component)
}
dependencies {
implementation(":hilt-android:2.48.1")
kapt(":hilt-compiler:2.48.1")
}
//Also remember to have the following data configuration,But there's usually a default
android{
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
}
I here directly new version of as create a new project, have used toml + ksl way, paste the following picture for reference.
toml:
in the app
Here's the catch.
That is, ksp and kapt together will lead to compilation failure, have to set the plug-in does not pass, and the above screenshots
annotate
@HiltAndroidApp
class MyApplication:Application() {
override fun onCreate() {
()
//...
}
}
Note the use of the
MyApplication
The object!
<application
android:name=".MyApplication"
//Omit the rest...
/>
3. Injecting objects
class MyApi @Inject constructor(){
fun sendApi(){
}
}
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
/**
* It can't be here.private,and is lazy loaded
*/
@Inject
lateinit var api: MyApi
override fun onCreate(savedInstanceState: Bundle?) {
(savedInstanceState)
setContentView(.activity_main)
}
}
supplementary note
We note that three notes appear above
-
@HiltAndroidApp
Use the -
@AndroidEntryPoint
Use this annotation on classes such as activity, as described below. -
@Inject
Used to inject objects and identify entities to be injected
included among these@HiltAndroidApp
is used in the application, while the
hilt has the following entry points.
- Application
- Activity
- Fragment
- View
- Service
- BroadcastReceiver
In other words, we can use dependency injection only in these classes
For application, we use@HiltAndroidApp
annotation, this step is necessary, otherwise the dependency injection will not work!
And the other.@AndroidEntryPoint
annotation, where you need to use the@Inject
To inject an object, you need to mark the current class with an annotation.@AndroidEntryPoint
, (as in the code example in the previous step)
Advanced Use
1. With reference to entity injection
Add a new constructor parameter to the previous MyApi.
class MyApi @Inject constructor(val client:Client){
fun sendApi(){
}
}
class Client @Inject constructor(){
fun config() {
}
}
To summarize: the entity that requires dependency injection, if there are other parameters, then ensure that the other parameters of the entity is also dependent on the injection can be
The MyApi above can also be written as follows.
class MyApi @Inject constructor(){
@Inject
lateinit var client: Client
fun sendApi(){
}
}
2. Interface type injection
import
import
import
import
import
interface ClientInterface{
fun config()
}
class MyClient @Inject constructor():ClientInterface{
override fun config() {
("ttt", "myclient config ")
}
}
class MyApi @Inject constructor(){
@Inject
lateinit var clientInterface: ClientInterface
fun sendApi(){
()
("ttt", "send api")
}
}
@Module
@InstallIn(ActivityComponent::class)
abstract class ClientModule {
@Binds
abstract fun createClient(myClient: MyClient): ClientInterface
}
The above defines aClientInterface
interface, we want to inject an implementation of this interface class MyClient
You have to add an extra ClientModule class, and the dependency injection will pass the corresponding method in this class.createClient
to inject
Here the class and method names are arbitrary, but the parameters in the method are the interface implementation class you want to inject, the return is the interface class, but also note that the class is an abstract class!
About @InstallIn annotation, in the following section will explain again, here first skipped, first this can be used
PS: Of course, this can also be not an interface type, change to an abstract class should also be able to!
3. Different instances of the same type are injected
Add a new class to the above interface type injection code for injection
class MyTwoClient @Inject constructor():ClientInterface{
override fun config() {
("ttt", "mytwoclient config ")
}
}
@Module
@InstallIn(ActivityComponent::class)
abstract class ClientModule {
@BindMyClient
@Binds
abstract fun createClient(myClient: MyClient): ClientInterface
/**
* Note that this method name cannot be used in conjunction with the abovecreateClientequal,Otherwise the compilation will fail!
*/
@BindMyTwoClient
@Binds
abstract fun createTwoClient(myClient: MyTwoClient): ClientInterface
}
@Qualifier
@Retention()
annotation class BindMyClient
@Qualifier
@Retention()
annotation class BindMyTwoClient
Modify the place where you want to inject the object: the
class MyApi @Inject constructor(){
/**
* Here we use @BindMyTwoClient to indicate that we want to inject a MyTwoClient instance.
*/
@BindMyTwoClient
@Inject
lateinit var clientInterface: ClientInterface
fun sendApi(){
()
("ttt", "send api")
}
}
4. External third-party entity injection
Here said the third party, refers to third-party libraries, because the library is basically encapsulated, code modification is not as free as the above, there may be no constructor, so how should we realize the injection?
Here's what you can do with the@Provides
annotation to implement the
import
import
import
import
import
import
class MyApi @Inject constructor(){
/**
* indicate clearly and with certaintyMyClient,Dependency injection will eventually call the latercreateNClient()method generates the object
*/
@Inject
lateinit var clientInterface: MyClient
fun sendApi(){
("ttt", "send api")
}
}
class MyClient {
fun config() {
("ttt", "myclient config ")
}
}
@Module
@InstallIn(ActivityComponent::class)
class ClientModule {
@Provides
fun createNClient(): MyClient{
//Here's a handy time delay.,I went straight through and created an instance of
return MyClient()
}
}
Unlike the interface type injection above, you need to pay attention to the following points.
- The class ClientModule is not an abstract class anymore.
-
@Provides
The annotated method, which is not an abstract method and does not use the@Inject
annotate
Or the method can take a parameter (dependency on another class).
@Module
@InstallIn(ActivityComponent::class)
class ClientModule {
@Provides
fun createNClient(): MyClient{
return MyClient()
}
/**
* myClientThis will also be automatically injected into our return data above.
*/
@Provides
fun createNewApi(myClient: MyClient): MyNewApi{
return MyNewApi(myClient)
}
}
class MyNewApi(myClient: MyClient)
Components and component scopes
present (sb for a job etc)
There's an @InstallIn on it, which translates to Install In.
@InstallIn(ActivityComponent::class)
: is to install the module into the Activity component; : is to install the module into the Activity component.
If we use @Inject in Service, we will get an error at compile time because ActivityComponent is restricted to be used only in activity.
Except, of course.ActivityComponent
For this component, we have other components available, as follows
Android class | subassemblies | scope (computing) |
---|---|---|
Application | SingletonComponent | @Singleton |
Activity | ActivityRetainedComponent | @ActivityRetainedScoped |
ViewModel | ViewModelComponent | @ViewModelScoped |
Activity | ActivityComponent | @ActivityScoped |
Fragment | FragmentComponent | @FragmentScoped |
View | ViewComponent | @ViewScoped |
View with @WithFragmentBindings annotation | ViewWithFragmentComponent | @ViewScoped |
Service | ServiceComponent | @ServiceScoped |
-
@Singleton
A constructor or function modified by it will always return the same instance of -
@ActivityRetainedScoped
It modifies a constructor or function that returns the same instance before and after the rebuild of the activity. -
@ActivityScoped
Constructors or functions modified by it return the same instance in the same Activity object. -
@ViewModelScoped
The constructor or function modified by it is consistent with the ViewModel rules.
Component Life Cycle
generated component | Timing of Creation | Timing of destruction |
---|---|---|
SingletonComponent | Application#onCreate() | Application destroyed |
ActivityRetainedComponent | Activity#onCreate() | Activity#onDestroy() |
ViewModelComponent | ViewModel Created | ViewModel destroyed |
ActivityComponent | Activity#onCreate() | Activity#onDestroy() |
FragmentComponent | Fragment#onAttach() | Fragment#onDestroy() |
ViewComponent | View#super() | View Destroyed |
ViewWithFragmentComponent | View#super() | View Destroyed |
ServiceComponent | Service#onCreate() | Service#onDestroy() |
Dependency Injection for Singleton Implementation
In general, our api global should be a singleton pattern, so the above can be changed to the following code.
@Module
@InstallIn(SingletonComponent::class)
class ClientModule {
@Singleton
@Provides
fun createNClient(): MyClient{
return MyClient()
}
}
The @Singleton above cannot be omitted.Omitting the equivalent of the default component you use is equivalent to creating a new instance every time you inject!
Then it is important to note that the following are incorrectly written.
@Module
@InstallIn(SingletonComponent::class)
class ClientModule {
@ActivityScoped //incorrect,Inconsistent with the current component's scope
@Provides
fun createNClient(): MyClient{
return MyClient()
}
}
@Module
@InstallIn(ActivityComponent::class)
class ClientModule {
@Singleton //incorrect,Inconsistent with the current component's scope
@Provides
fun createNClient(): MyClient{
return MyClient()
}
}
Component scopes can be used not only in modules, but also to modify constructors.
@ActivityScoped
class Hardware @Inject constructor(){
fun printName() {
println("I'm fish")
}
}
means that Hardware will only have one instance in the same Activity.
Hierarchy of components
Components have a hierarchical use, such as the global api above, we can be injected elsewhere component scopes or Activity, fragment in the use of injection, the following code.
@Module
@InstallIn(SingletonComponent::class)
class ClientModule {
@Singleton
@Provides
fun createNClient(): MyClient{
return MyClient()
}
}
@ActivityScoped
class MyNewApi @Inject constructor(myClient: MyClient)
The specific hierarchy of the relationship structure is shown in the following figure.
Injecting an application or activity
When we need to pass an application or activity to the constructor, we can use the@ApplicationContext
cap (a poem)@ActivityContext
Qualifier.
As in the following code.
class AnalyticsServiceImpl @Inject constructor(
@ApplicationContext context: Context
) : AnalyticsService { ... }
// The Application binding is available without qualifiers.
class AnalyticsServiceImpl @Inject constructor(
application: Application
) : AnalyticsService { ... }
class AnalyticsAdapter @Inject constructor(
@ActivityContext context: Context
) { ... }
// The Activity binding is available without qualifiers.
class AnalyticsAdapter @Inject constructor(
activity: FragmentActivity
) { ... }
Special Usage
It seems to be a matter of customizing the entry class and then implementing an extension method for the application.
@Module
@InstallIn(SingletonComponent::class)
object PlayServiceModule {
fun (): PlayerController {
return accessEntryPoint<PlayerControllerEntryPoint>().playerController()
}
@EntryPoint
@InstallIn(SingletonComponent::class)
interface PlayerControllerEntryPoint {
fun playerController(): PlayerController
}
}
Integration with ViewModel
Add the @HiltViewModel annotation to the ViewModel and use the @Inject annotation in the constructor of the ViewModel object.
@HiltViewModel
class ExampleViewModel @Inject constructor(
private val savedStateHandle: SavedStateHandle,
private val repository: ExampleRepository
) : ViewModel() {
...
}
Then, an activity or fragment annotated with @AndroidEntryPoint can get a ViewModel instance as usual using ViewModelProvider or by viewModels() KTX extensions:
@AndroidEntryPoint
class ExampleActivity : AppCompatActivity() {
private val exampleViewModel: ExampleViewModel by viewModels()
...
}
consultation
- Jetpack's newest addition, an article that takes you through Hilt and Dependency Injection - Nuggets
- Playing with Jetpack Dependency Injection Framework - Hilt - Nuggets
- Android uses Hilt dependency injection to make your code unreadable - Nuggets
- Implementing Dependency Injection with Hilt | Android Developers