Location>code7788 >text

Jetpack Architecture Components Learning (5) - Hilt injection framework use

Popularity:122 ℃/2024-08-17 14:45:22

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.

  1. Maybe it's better for large projects with multiple modules.
  2. MMVM/MVI architecture apps are also better suited for
  3. 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 theMyApplicationThe 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@HiltAndroidAppis 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@HiltAndroidAppannotation, this step is necessary, otherwise the dependency injection will not work!

And the other.@AndroidEntryPointannotation, where you need to use the@InjectTo 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 aClientInterfaceinterface, 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.createClientto 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@Providesannotation 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.

  1. The class ClientModule is not an abstract class anymore.
  2. @ProvidesThe annotated method, which is not an abstract method and does not use the@Injectannotate

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.ActivityComponentFor 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