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
@Inject
Describe the need - Rely on theThe disclosing partyTo provide implementation roles for dependent instances, such as using
@Provides
Describe the source - Rely on theInto the party, injecting provider implementations into demander roles, such as usage
@Component
Describes 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 through
NetworkModule
Provide a singleton Retrofit interface to make a movie search request to OMDB - LocalData rely on
AnalysisModule
The provided AnalysisService interface logs in the selected movieRoomModule
In 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.
Ⅰ. @AndroidEntryPoint
The 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 supportedComponentActivity
The 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.Fragment
Package 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 toBrocastReceiver
Generate 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)View
Field 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.ContentProvider
You 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.ActivityRetainedComponent
The 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 using
viewModels()
The gradle file is not imported. The gradle file is not importedfragment-ktx
The dependence of - Be sure to check if the viewModel fails and is prompted to not include a default constructor
hilt-compiler
Is 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…