Hilt continued to refine Jetpack’s layout as an Android exclusive DI framework. It has many improvements over its predecessor, Dagger2, but also many limitations, and this article will answer them one by one.

The origin of the Hilt

Let’s start with the official description of Hilt.

Hilt provides a standard way to incorporate Dagger dependency injection into an Android application. To simplify Dagger-related infrastructure for Android apps. To create a standard set of components and scopes to ease setup, readability/understanding, and code sharing between apps. To provide an easy way to provision different bindings to various build types (e.g. testing, debug, or release).

As described, Hilt is a dependency injection solution built for Android apps based on a Dagger(Dagger2). It simplifies the use of annotations while retaining the performance benefits of Dagger2’s compile-time injection. At the same time, the Android framework class has been optimized.

Before we expand on Hilt, let’s briefly review the roles and processes of dependency injection.

Dependency injection process

  • Rely on theThe demand side, by constructing parameters or fields that depend on other instances of the role, generally used@InjectDescribe the need
  • Rely on theThe disclosing partyTo provide implementation roles for dependent instances, such as using@ProvidesDescribe the source
  • Rely on theInto the party, injecting provider implementations into demander roles, such as usage@ComponentDescribes the injection component

The improvement of the Hilt

① Define application components

Adding the @HiltAndroidApp annotation to the Application tells Hilt to generate application-level components, automatically implementing the dependency injection starting point and eliminating the need for manual invocation of Dagger2.

@HiltAndroidApp
class MyApplication : Application() {... }Copy the code

② Define the Android framework class components

The @AndroidEntryPoint annotation is used to generate Hilt components for Android framework classes such as Activities, fragments, and Services, eliminating the need to define the corresponding SubComponent template.

@AndroidEntryPoint
open class BaseActivity() : AppCompatActivity() {... }Copy the code

③ Bind the life cycle

The @installin annotation tells Hilt which Android class each module will be used in or bound to. For example, specifying value as ApplicationComponent indicates that the module instantiates only one instance (singleton) throughout the application cycle. Others include the ActivityComponent that is bound to the Activity lifecycle.

@Module
@InstallIn(ApplicationComponent::class)
class NetworkModule {... }Copy the code

④ Preset scope

Annotations like @Singleton and @ActivityRetainedScoped are used to declare the scope of this injection. For example, the Activity is redrawn because of the Configuration Change but the dependencies of the @activityRetainedScoped annotation are not re-created.

@ActivityRetainedScoped
class MovieAdapter @Inject constructor() {... }@AndroidEntryPoint
class DemoActivity : AppCompatActivity() {
    @Inject lateinit var movieAdapter: MovieAdapter 
    ...
}
Copy the code

(5) into the Context

The @applicationContext and @ActivityContext annotations allow you to inject Context instances quickly without having to provide your own implementation.

class MovieAdapter @Inject constructor(@ActivityContext private valcontext: Context) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {... }Copy the code

⑥Jetpack component support

Hilt implements extensions to help us inject ViewModel and WorkManager dependencies. The @ViewModelInject annotation, for example, tells Hilt that the ViewModel instance needs to be injected.

class MovieViewModel @ViewModelInject constructor(private val repository: Repository,
                                                  varmovieAdapter: MovieAdapter ) : ViewModel() {... }Copy the code

Hilt, like Dagger2, supports @Qualifier to define annotations for multi-type injection. There is no difference in the use of @inject, @provides and @Sharing, which will not be further described.

Those interested can consult the official documentation for more details.

Developer. The android. Google. Cn/training/DE…

In actual combat

Use the OMDB API to demonstrate Hilt usage.

The ViewModel is injected with a Repository via @ViewModelInject. The Repository relies on RemoteData and LocalData.

  • RemoteData throughNetworkModuleProvide a singleton Retrofit interface to make a movie search request to OMDB
  • LocalData rely onAnalysisModuleThe provided AnalysisService interface logs in the selected movieRoomModuleIn the Room Database provided

address

Github.com/ellisonchan…

Block diagram & screenshot

The limitation of Hilt

Improvements to Hilt inevitably come with limitations that allow Hilt to play to its advantage.

Ⅰ. @AndroidEntryPointThe limits of

@ AndroidEntryPoint restrictions Limit content
Attachment class Attached classes also need to add @AndroidEntryPoint
Activity Only annotate ComponentActivity subclass Activity
Fragment Fragments can only be annotated on androidx, but they are not supported across fragments
The base class AndroidEntryPoint can be added uniformly to base classes, but not to abstract classes

① If the framework class has a dependency class, it also needs to be added@AndroidEntryPoint

If you add @AndroidEntryPoint to Framgent but don’t add it to your Activity, the following exception will occur when you start your Fragment.

Hilt Fragments must be attached to an @AndroidEntryPoint Activity.

Will ensure that after principle is that the fragments in the attach fragments attached Activity implements GeneratedComponentManager interfaces, namely whether added @ AndroidEntryPoint annotation.

(2) Only self-expansion is supportedComponentActivityThe Activity of

If the Activity annotated at @AndroidEntryPoint is not a subclass of ComponentActivity, it will fail at compile time.

Activities annotated with @AndroidEntryPoint must be a subclass of androidx.activity.ComponentActivity.

AndroidEntryPoint annotated Activities support ViewModel injection, and the implementation of ViewModel relies entirely on ComponentActivity, so this restriction is necessary. After all, you already use Jetpack, and using an old component that is so important to your Activity is just too undetermined.

(3) Only self-expansion is supportedandroidx.FragmentPackage of fragments

As with Activity restrictions, using AOSP fragments won’t let you pass the compile phase.

@AndroidEntryPoint base class must extend ComponentActivity, (support) Fragment, View, Service, or BroadcastReceiver.

In fact AOSP fragments have been Deprecated since Android 9 due to the lack of many features including Lifecycle, ViewMode, etc. You can’t pair it with ComponentActivity.

④ But not supportedRetained Fragment

A Fragment that calls setRetainInstance(true) is a Retained Fragment. This means that the Configuration Change caused the view to be destroyed but not the Fragment itself.

However, if Retained Fragment @AndroidEntryPoint is accidentally added, the following exceptions may occur when repainting caused by horizontal and vertical screen switching.

onAttach called multiple times with different Context! Hilt Fragments should not be retained.

The Configuration Change causes the Activity to rebuild, but the Retained Fragment instance remains. This means that the same Fragment instance is injected with multiple dependencies, which doesn’t make sense. So Hilt creates a Context instance based on the attached Activity before injecting a Fragment for the first time, and then checks if this instance is empty to ensure that the Fragment is new each time.

⑤ The base class of the framework class can add @AndroidEntryPoint uniformly, but the abstract class does not need

By adding this annotation once to the base class of the framework class, each Hilt can generate a unified component to inject dependencies into each subclass without additional additions to each subclass. However, this annotation cannot be used if the base class is abstract, and each subclass still needs to be added separately. Otherwise, a compilation error occurs.

Ii. Injection limits of Android framework classes

Injection limits for Android framework classes Limit content
Injection pattern Field injection only
Field modifier Not for the private
BrocastReceiver No separate components are generated
View Binding to ActivityComponent by default
ContentProvider The @AndroidentryPoint annotation cannot be used directly

(1) To inject instances into framework classes such as Activity, you need to use field injection. Instances of Android specific classes such as Application and Activity are created by the system, and cannot be injected through constructors.

② The injected fields cannot be private

It doesn’t matter if you accidentally declare injected fields private; compile-time will warn you.

Execution failed for task ‘:app:kaptDebugKotlin’. A failure occurred while executing org.jetbrains.kotlin.gradle.internal.KaptExecution.

(3) unable toBrocastReceiverGenerate separate Component components

Unlike activities and Fragments, Hilt directly injects dependencies into BrocastReceiver from the ApplicationComponent. In principle, the creation and callbacks of BrocastReceiver instances are scheduled directly by ApplicationThread.

(4)ViewField injection is bound to the ActivityComponent by default

If the View comes from a Fragment, you can add @WithFragmentBindings to @AndroidEntryPoint to bind the injection precisely to the FragmentComponent.

5.ContentProviderYou cannot use Hilt annotations directly

Among the four components, only the instance of ContentProvider is created before Application, but the injection starting point of the entire Application is the Application component, so it cannot directly provide injection support for ContentProvider.

However, if the ContentProvider does have an injection requirement, you need to define the @entryPoint annotation interface and specify the component to which the dependency is bound through @installin, similar to Dagger2.

ⅲ. Attention to life cycle

Life cycle attention Limit content
Default binding Each injection creates a new instance
ActivityRetainedComponent Destroy the last time you destroy

The default binding creates a new instance on each injection.

Due to memory overhead, all bindings are not scoped by default, meaning that a new instance is provided where each dependency is injected. If the dependent instance has a specific usage scenario or scope, you can specify a scope for this injection, such as the @Singleton scope of one instance for the entire application lifecycle.

2.ActivityRetainedComponentThe component is created the first time onCreate() is called and destroyed the last time Activity#onDestroy() is called.

ActivityRetainedComponent components in the Configuration Change, leading to the Activity still exist after painting, life cycle is longer than ActivityComponent components. You can add the @ActivityRetainedScope annotation to bind this component.

One caveat is that if you use @ViewModelInject to provide ViewModel dependencies, you don’t need to annotate dependent instances with @ActivityRetainedScope. Because it has already been automatically bound to ActivityRetainedComponent components.

ⅳ. ViewModel Injection points of attention

Injecting dependencies into ViewModel using @ViewModelInject pays special attention to the following.

  • If you are usingviewModels()The gradle file is not imported. The gradle file is not importedfragment-ktxThe dependence of
  • Be sure to check if the viewModel fails and is prompted to not include a default constructorhilt-compilerIs the comment handler declared in a Gradle file
  • If the following compilation error occurs, remember to declare the JVM version 1.8 in gradle kotlinOptions

Cannot inline bytecode built with JVM target 1.8 into bytecode that is being built with JVM target 1.6. Please specify proper ‘-jvm-target’ option

conclusion

The improvement over The Dagger2 shows Hilt’s many advantages.

  • Highly encapsulated injection of framework classes avoids manual initialization of components
  • Preset full scope and convenient Context injection
  • Support for Jetpack components

When we need to import DI in the Android App, we can give it priority. Hilt is not perfect, however. In addition to the limitations outlined above, Hilt also has inherent disadvantages, such as its inability to be applied to dynamic functional module projects.

Choose between the small and beautiful Hilt or the big and powerful Dagger2 based on your actual needs.

DMEO

Github.com/ellisonchan…

The resources

Developer. The android. Google. Cn/training/DE… Developer. The android. Google. Cn/training/DE… Mp.weixin.qq.com/s/VKyyNqAPF… Guolin.blog.csdn.net/article/det…