This article continues to explain the use of Hilt by introducing the common annotations of Hilt and some of the pitfalls encountered during the implementation process, how Hilt binds to Android framework classes, and their lifecycle. The code has been uploaded to GitHub: HiltWithAppStartupSimple. If it helps you, please give me a like in the upper right corner of the warehouse.

Hilt is a bit more complicated and difficult to understand. Before reading this article, you must first read the Hilt practice (1), a new member of Jetpack. In order to save space, this article will ignore the Hilt environment configuration process and other previous articles.

In addition, if you want to learn more about the practice and principles of Google’s two new Jetpack members, Startup and Paging3, you can click the link below to check out.

  • AndroidX App Startup practice and principle analysis of Jetpack members
  • Jetpack member Paging3 Data Practices and source code Analysis (1)
  • Jetpack Member Paging3 Network Practice and Principle Analysis (II)
  • Jetpack member Paging3 uses RemoteMediator to load network page data and update it to the database.
  • Code address: https://github.com/hi-dhl/AndroidX-Jetpack-Practice

In this article you will learn the following:

  • What are annotations?

  • How are @assist annotations and SavedStateHandle used?

  • How do I implement interface injection using the @Sharing annotation?

  • What is the difference between @Binds and @provides?

  • Use of the @qualifier?

    • Custom qualifiers@qualifers
    • Predefined qualifiers@qualifers
  • How is the component scope @scopes used?

  • How do I perform dependency injection in classes not supported by Hilt?

    • HiltHow andContentProviderTogether?
    • HiltHow andApp StartupTogether?

Hilt is developed based on Dagger, which should look a lot like Dagger if you know your Dagger friends, but unlike Dagger, Hilt integrates the Jetpack library with the Android framework class and removes most of the template code, Let the developer just focus on how to bind without managing all the Dagger configurations.

In the previous article, WE introduced how Hilt is bundled with Android framework classes and their lifecycle. This article will show how Hilt is bundled with Jetpack components (ViewModel, App Startup). Before we begin, let’s take a look at annotations.

What are annotations

A friend asked me on WX before, but I don’t know much about annotations, so I want to briefly mention them here.

Annotations are special “comments” placed before classes, methods, fields, and parameters in Java source code. Annotations can be packaged by the compiler into a class file and read at compile, class load, and run time.

The three common annotations @Override, @deprecated and @SuppressWarnings

  • @Override: Make sure that the subclass overrides the parent class’s method, and the compiler checks that the method is implemented correctly.
  • @Deprecated: indicates that a class or method is obsolete. The compiler checks if an outdated method is used and gives a warning.
  • @SuppressWarnings: The compiler ignores the generated warnings.

How does Hilt work with ViewModel?

After a brief introduction to how Hilt works with the ViewModel in the previous article, we will continue with another important ViewModel parameter, SavedStateHandle, which requires adding dependencies.

Add the following code to the build.gradle file in the App module.

Implementation 'androidx. Hilt: hilt - lifecycle - viewmodel: 1.0.0 - alpha01' kapt 'androidx. The hilt: hilt - compiler: 1.0.0 - alpha01'Copy the code

Koltin uses kapt and Java uses annotationProcessor.

Note: This is not mentioned in the Google documentation, if you are using Kotlin you need to add the following code to the build.gradle file in the App module, otherwise calling by viewModels() will compile however.

// For Kotlin projects kotlinOptions {jvmTarget = "1.8"}Copy the code

Provide a ViewModel with the @ViewModelInject annotation in the ViewModel constructor. If SavedStateHandle is needed, add the SavedStateHandle dependency using the @Assist annotation. The code is shown below.

class HiltViewModel @ViewModelInject constructor( private val tasksRepository: Repository, //SavedStateHandle Saves and recovers data when a process is terminated @Assisted Private Val SavedStateHandle: SavedStateHandle) : ViewModel() {// getLiveData gets a MutableLiveData associated with the key // MutableLiveData is updated when the value associated with the key changes. Private val _userId: MutableLiveData < String >. = savedStateHandle getLiveData (USER_KEY) / / exposed immutable LiveData val userId: LiveData<String> = _userId companion object { private val USER_KEY = "userId" } }Copy the code

The user’s userId is stored in SavedStateHandle and the corresponding data is saved and restored when the process is terminated.

What is SavedStateHandle? What problem does SavedStateHandle solve?

Activities and fragments are usually destroyed in one of three ways (from Google) :

  • Permanently departs from current interface:The user navigates to another interface or simply closes the Activity (by clicking the back button or performing the action invoked)finish()Methods). The corresponding Activity instance is permanently shut down.
  • The Activity configuration is changed: actions such as rotating the screen require the Activity to be rebuilt immediately.
  • When an application is in the background, its process is killed by the system: This occurs when the device is running out of memory and the system needs to free some memory. After the process is killed in the background, the Activity needs to be rebuilt when the user returns to the application.

The ViewModel handles the second case for you, because in this case the ViewModel is not destroyed, and in the third case the ViewModel is destroyed, when the process is killed in the background, Use onSaveInstanceState() as an alternate way to save data.

SavedStateHandle was created to solve the problem of saving and recovering data from App process terminations. The ViewModel does not need to send and receive state to the Activity. Instead, you can now handle saving and restoring data in the ViewModel.

SavedStateHandle is like a Bundle. It is a key-value map of data. The SavedStateHandle is contained in the ViewModel and survives when the background process terminates. Any data previously stored in onSaveInstanceState() can now be stored in SavedStateHandle.

Interface injection using the @Sharing annotation?

There are two ways to inject interface instances using annotations @Binds and @provides, In the last article, New Jetpack member Hilt Practices (I) started out by sharing how Hilt is used with Room and with third party components. Here we introduce how to use the annotation @Binds.

Interface WorkService {fun init()} Because Hilt needs to know how to provide an instance of WorkServiceImpl */ class WorkServiceImpl @inject constructor() : WorkService { override fun init() { Log.e(TAG, "I am an WorkServiceImpl")}} @ Module @ InstallIn (ApplicationComponent: : class) / / used ActivityComponent here, So the WorkServiceModule is bound to the ActivityComponent lifecycle. The abstract Class WorkServiceModule {/** * @Cursorbinding annotation tells Hilt which implementation to use when it needs to provide interface instances ** The bindAnalyticsService function needs to provide Hilt with the following information * 1. The function return type tells Hilt which interface instance is provided * 2. The function argument tells Hilt which implementation is provided */ @Binds Abstract Fun bindAnalyticsService(workServiceImpl: WorkServiceImpl ): WorkService }Copy the code

The following two pieces of information are required when using the annotation @Sharing:

  • Function parameters tell Hilt the implementation class of the interface, for example, the parameter WorkServiceImpl is the implementation class of the interface WorkService.
  • The function return type tells Hilt which instance of the interface is provided.

What is the difference between the annotation @Binds and the annotation @provides?

  • @BindsThe implementation class of the interface needs to be explicitly specified in the method arguments.
  • @Provides: Does not need to specify the implementation class of the interface in the method argument. It is implemented by third-party frameworks and is usually used to bind to third-party frameworks.Retrofit,RoomEtc.)
Abstract Fun bindAnalyticsService(workServiceImpl: workServiceImpl): @provides fun providePersonDao(Application: application): PersonDao { return Room .databaseBuilder(application, AppDataBase::class.java, "dhl.db") .fallbackToDestructiveMigration() .allowMainThreadQueries() .build().personDao() } @Provides fun provideGitHubService(): GitHubService { return Retrofit.Builder() .baseUrl("https://api.github.com/") .addConverterFactory(GsonConverterFactory.create()) .build().create(GitHubService::class.java) }Copy the code

Use of the @qualifier annotation

From Google: @Qualifier is an annotation used to identify a specific binding for a type when more than one binding is defined for that type.

In other words, @qualifier declares the same type and can be bound in more than one place. I’ve divided qualifiers into two types.

  1. Custom qualifiers
  2. Predefined qualifier

Use of custom qualifiers

Let’s start by declaring two different implementations with the @qualifier annotation.

// Provides a corresponding type instance for each declared Qualifier, which defines the annotation lifecycle using @qualifier // @Qualifier together with @Binds or @provides. Corresponding to three values (SOURCE, BINARY, RUNTIME) @ Retention (AnnotationRetention. BINARY) the annotation class RemoteTasksDataSource / / the name of the annotation, Use it directly behind the @ the Qualifier @ Retention (AnnotationRetention. RUNTIME) the annotation class LocalTasksDataSourceCopy the code
  • Qualifier: Provides a corresponding type instance for each declared Qualifier, which is shared with @Binds or @provides

  • @Retention: Defines the annotation life cycle, corresponding to three values (SOURCE, BINARY, RUNTIME)

    • AnnotationRetention.SOURCE: only compile-time, not stored in binary output.
    • AnnotationRetention.BINARY: stored in binary output, but not visible to reflection.
    • AnnotationRetention.RUNTIME: stored in binary output, visible to reflection.

Usually our custom annotations are RUNTIME, so be sure to use @Retention(retentionPolicy.runtime) annotation

Take a look at the example of @qualifier and @provides used together to define two methods that have the same return type but different implementations, and the Qualifier marks them as two different bindings.

@Singleton @RemoteTasksDataSource @Provides fun provideTasksRemoteDataSource(): DataSource {// same return RemoteDataSource() // different implementation} @singleton @localTasksdatasource @provides fun provideTaskLocalDataSource(appDatabase: AppDataBase): DataSource {return LocalDataSource(appDatabase.persondao ()) // different implementation}Copy the code

After declaring the @qualifier annotation, we can use the two @qualifiers declared. For example, define a Repository constructor that passes in two different implementations of the @qualifier annotation declaration.

@Singleton
@Provides
fun provideTasksRepository(
    @LocalTasksDataSource localDataSource: DataSource,
    @RemoteTasksDataSource remoteDataSource: DataSource
): Repository {
    return TasksRepository(
        localDataSource,
        remoteDataSource
    )
}
Copy the code

In the provideTasksRepository method, the parameters passed in are DataSource, but the @qualifier annotation previously declared their different implementations.

Predefined qualifier

Hilt provides some predefined qualifiers. For example, you may need different contexts (Appliction, Activity) in different situations. Hilt provides the @applicationContext and @activityContext qualifiers.

class HiltViewModel @ViewModelInject constructor(
    @ApplicationContext appContext: Context,
    @ActivityContext actContext: Context,
    private val tasksRepository: Repository,
    @Assisted private val savedStateHandle: SavedStateHandle
)
Copy the code

Use of the component scope @scopes

By default, all bindings in Hilt are scopeless, meaning that each time an application requests a binding, Hilt creates a new instance of the required type.

The @scopes function provides the same instance within the specified scope (Application, Activity, etc.).

Hilt also allows you to limit the scope of a binding to a particular component. Hilt only creates a scope binding once for each instance of the component to which the binding is scoped. All binding requests share the same instance.

@Singleton
class HiltSimple @Inject constructor() {
}
Copy the code

HiltSimple declares its scope with @singleton, so provide the same instance within the Application scope, as shown in the code below. You can run the Demo to see the output.

MainActivity: com.hi.dhl.hilt.appstartup.di.HiltSimple@8f75417
HitAppCompatActivity: com.hi.dhl.hilt.appstartup.di.HiltSimple@8f75417
Copy the code

Note: the binding component range can be very expensive, because provide object will be kept in memory, until the component is destroyed, should try to reduce the use of binding component in the application scope, within a certain range for required to use the same instance of binding, or to create the binding, the high cost of using the component range of binding is appropriate.

The following table lists the scopes corresponding to the Scope annotation for each generated component.

Android class Generated component Scope
Application ApplicationComponent @Singleton
View Model ActivityRetainedComponent @ActivityRetainedScope
Activity ActivityComponent @ActivityScoped
Fragment FragmentComponent @FragmentScoped
View ViewComponent @ViewScoped
View annotated with @WithFragmentBindings ViewWithFragmentComponent @ViewScoped
Service ServiceComponent @ServiceScoped

Perform dependency injection in classes not supported by Hilt

Hilt supports the most common Android classes, Application, Activity, Fragment, View, Service, BroadcastReceiver, etc., but you may need to perform dependency injection in classes that Hilt does not support. In this case you can use the @entryPoint annotation to create and Hilt will provide the corresponding dependencies.

@EntryPoint: Entry points can be created using @EntryPoint annotations that allow Hilt to use dependent objects that Hilt cannot provide in dependencies.

For example, Hilt does not support ContentProviders. If you want to get Hilt’s dependencies in a ContentProvider, you can define an interface and add an @entryPoint annotation. Then add the @InstallIn annotation to specify the scope of the Module, as shown below.

@EntryPoint @InstallIn(ApplicationComponent::class) interface InitializerEntryPoint { fun injectWorkService(): WorkService companion object { fun resolve(context: Context): InitializerEntryPoint { val appContext = context.applicationContext ? : throw IllegalStateException() return EntryPointAccessors.fromApplication( appContext, InitializerEntryPoint::class.java ) } } }Copy the code

EntryPointAccessors provides access to four static methods: fromActivity, fromApplication, fromFragment, fromView, and more

EntryPointAccessors provides four static methods. The first argument is the scope of the module specified by the @InstallIn annotation on the @EntryPoint interface. The @installIn annotation in the InitializerEntryPoint interface specifies that the scope of the Module is ApplicationComponent. So we should use the static method fromApplication provided by EntryPointAccessors.

class WorkContentProvider : ContentProvider() { override fun onCreate(): Boolean { context? .run { val service = InitializerEntryPoint.resolve(this).injectWorkService() Log.e(TAG, "WorkContentProvider ${service.init()}") } return true } ...... }Copy the code

The dependencies provided by Hit can be retrieved by calling the fromApplication method in the EntryPointAccessors class in the ContentProvider.

How does Hilt work with App Startup

App Startup provides an InitializationProvider by default. InitializationProvider inherits ContentProvider. So Hilt is used in App Startup the same way ContentProvider is used.

class AppInitializer : Initializer<Unit> {


    override fun create(context: Context): Unit {
        val service = InitializerEntryPoint.resolve(context).injectWorkService()
        Log.e(TAG, "AppInitializer ${service.init()}")
        return Unit
    }

    override fun dependencies(): MutableList<Class<out Initializer<*>>> =
        mutableListOf()

}
Copy the code

AndroidX App Startup is a static method that uses EntryPointAccessors to get a dependency provided by Hit. This is a static method that uses EntryPointAccessors

conclusion

Now that I’ve covered the use of Hilt annotations, the code has been uploaded to GitHub: HiltWithAppStartupSimple.

HiltWithAppStartupSimple includes the examples used in this article and the new Jetpack member Hilt Practice (1). If you haven’t seen it before, take a look at it first, and the code will become clearer later.

Hilt is developed on the basis of Dagger, which is much easier to get started than Dagger. There is no need to manage all the configuration problems of Dagger. However, the threshold of getting started is quite high, especially for Hilt annotations, which need to understand the meaning of each annotation to correctly use and avoid resource waste.

This article and the previous article on Hilt Practice (1), a new member of Jetpack, have been redesigned. Many of the cases provided by Google are really difficult to understand. I hope these two articles can help you quickly get started with Hilt, and there will be more practical cases later.

Plan to establish a most complete and latest AndroidX Jetpack related components of the actual combat project and related components of the principle of analysis article, is gradually increasing Jetpack new members, the warehouse continues to update, you can go to check: Androidx-jetpack-practice, if this warehouse is helpful to you, please give me a “like” in the upper right corner of the warehouse, and I will finish more project practices of new members of Jetpack later.

conclusion

Committed to share a series of Android system source code, reverse analysis, algorithm, translation, Jetpack source related articles, is trying to write a better article, if this article is helpful to you to give a star, to learn together, looking forward to growing with you.

algorithm

Since LeetCode has a large question bank, hundreds of questions can be selected for each category. Due to the limited energy of each person, it is impossible to brush all the questions. Therefore, I sorted the questions according to the classic types and the difficulty of the questions.

  • Data structures: arrays, stacks, queues, strings, linked lists, trees…
  • Algorithms: Search algorithm, search algorithm, bit operation, sorting, mathematics,…

Each problem will be implemented in Java and Kotlin, and each problem has its own solution ideas, time complexity and space complexity. If you like algorithms and LeetCode like me, you can pay attention to my LeetCode problem solution on GitHub: Leetcode-Solutions-with-Java-And-Kotlin, come to learn together And look forward to growing with you.

Android 10 source code series

I’m writing a series of Android 10 source code analysis articles. Knowing the system source code is not only helpful in analyzing problems, but also very helpful in the interview process. If you like to study Android source code as MUCH as I do, You can follow my Android10-source-Analysis on GitHub, and all articles will be synchronized to this repository.

  • How is APK generated
  • APK installation process
  • 0xA03 Android 10 source code analysis: APK loading process of resource loading
  • Android 10 source code: APK
  • Dialog loading and drawing process and use in Kotlin, DataBinding
  • WindowManager View binding and architecture
  • 0xA07 Android 10 source code analysis: Window type and 3d view hierarchy analysis
  • More……

Android Apps

  • How to package Kotlin + Android Databinding in your project
  • Bye-bye buildSrc, embrace Composing builds for faster Android builds
  • Few people know Kotlin’s technique and principle analysis
  • AndroidX App Startup practice and principle analysis of Jetpack’s latest member
  • Jetpack member Paging3 Practice and Source Code Analysis (PART 1)
  • Jetpack New Member Paging3 Network Practice and Principle Analysis (II)
  • Jetpack’s new member Hilt practices (1) Starting a pit

Select a translation

At present, I am sorting out and translating a series of selected foreign technical articles. Besides translation, many excellent English technical articles provide good ideas and methods. Every article has a part of the translator’s thinking and a deeper interpretation of the original text. You can pay attention to my Technical-Article-Translation on GitHub, and articles will be synchronized to this warehouse.

  • [Google engineers] just released a new Fragment feature, “New ways to transfer Data between Fragments” and source code analysis
  • How does FragmentFactory elegantly use Koin and partial source code analysis
  • [2.4K Start] Drop Dagger to Koin
  • [5K +] Kotlin’s performance optimization stuff
  • Decrypt RxJava’s exception handling mechanism
  • [1.4K+ Star] Picasso
  • More……

Tool series

  • Shortcuts to AndroidStudio that few people know
  • Shortcuts to AndroidStudio that few people know
  • All you need to know about ADB commands
  • 10 minutes introduction to Shell scripting
  • Dynamically debug APP based on Smali file Android Studio
  • The Android Device Monitor tool cannot be found in Android Studio 3.2