Dagger is used in the componentalized AwesomeGithub project to reduce manual dependency injection code. While it automates dependency management, it’s still a bit of a chore if you’ve written it. The project is littered with Components, which reminds me of the interface definition of the traditional MVP model.

In short, it is laborious, and there are many, many similar definitions. Perhaps Google has realized this and recently announced Hilt.

Hilt

In case you haven’t heard of Hilt, what is it?

Hilt is a dependency injection library for Android that reduces boilerplate code for performing manual dependency injection in projects.

Hilt provides a standard way to use DI (dependency injection) in your application by providing a container for each Android class in your project and automatically managing its life cycle. Hilt is built on top of Dagger and thus has the compile time correctness, run time performance, and scalability of Dagger.

So some of you might be wondering, why would you want Hilt’s Dagger if you already have a Dagger?

Hilt and Dagger have the same primary goal:

  1. To simplify theAndroidThe application ofDaggerRelated infrastructure.
  2. Create a standard set of components and scopes to simplify setup, improve readability, and share code between applications.
  3. Provides an easy way to configure different bindings for various build types, such as test, debug, or release.

But Android instantiates many component classes, such as activities, so using a Dagger in an app requires a lot of boilerplate code. Hilt can reduce this boilerplate code.

The optimizations Hilt does include

  1. You don’t have to write a lot ofComponentcode
  2. ScopeAlso with theComponentAutomatic binding
  3. Predefined bindings, for exampleApplicationwithActivity
  4. Predefined qualifiers, for example@ApplicationContextwith@ActivityContext

The Dagger in AwesomeGithub is used for comparison.

Rely on

Add Hilt dependencies to the project before using it.

First, add the hilt-Android-gradle-plugin to your project’s root build.gradle file:

buildscript {
    ...
    dependencies {
        ...
        classpath 'com. Google. Dagger hilt - android - gradle - plugin: 2.28 alpha'}}Copy the code

Then, apply the Gradle plugin and add the following dependencies to the app/build. Gradle file:

. apply plugin:'kotlin-kapt'
apply plugin: 'dagger.hilt.android.plugin'
 
android {
    ...
}
 
dependencies {
    implementation Com. Google. "dagger hilt - android: 2.28 alpha." "
    kapt Com. Google. "dagger hilt - android - the compiler: 2.28 alpha." "
}
Copy the code

The Application class

With a Dagger, you need an AppComponent singleton that all the other subComponents in your project depend on, so it looks something like this in AwesomeGithub

@Singleton
@Component(
    modules = [
        SubComponentModule::class,
        NetworkModule::class,
        ViewModelBuilderModule::class
    ]
)
interface AppComponent {
 
    @Component.Factory
    interface Factory {
        fun create(@BindsInstance applicationContext: Context): AppComponent
    }
 
    fun welcomeComponent(): WelcomeComponent.Factory
 
    fun mainComponent(): MainComponent.Factory
     
    ...
 
    fun loginComponent(): LoginComponent.Factory
 
}
 
@Module(
    subcomponents = [
        WelcomeComponent::class,
        MainComponent::class,
        ...
        LoginComponent::class
    ]
)
object SubComponentModule
Copy the code

I have omitted most of the above, doesn’t it seem a lot, and the most important thing is that many of the repeated structures are basically the same. So Hilt takes this one step further, turning the repetitive writing into build-time automating.

What Hilt does is simply add a few comments

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

All Hilt applications must include an Application annotated with @hiltAndroidApp. It will replace the AppComponent in the Dagger.

Android class

For Android classes, using the Dagger requires defining the SubComponent and attaching it to the Application class. Here’s an example of WelcomeActivity.

@Subcomponent(modules = [WelcomeModule::class])
interface WelcomeComponent {
    @Subcomponent.Factory
    interface Factory {
        fun create(): WelcomeComponent
    }

    fun inject(activity: WelcomeActivity)
}
Copy the code

The Module part will be mentioned later

Let’s look at the implementation of Hilt

@AndroidEntryPoint
class MainActivity : BaseHiltActivity<ActivityMainBinding, MainVM>() { ... }
Copy the code

All Hilt has to do is add the @AndroidentryPoint comment.

Surprised, combined with the above two annotations, we replace the Dagger implementation, do you feel the brevity of Hilt now? It can also reduce the learning cost for beginners.

Hilt currently supports the following Android classes

  1. Application (@HiltAndroidApp)
  2. Activity
  3. Fragment
  4. View
  5. Searvice
  6. BroadcastReceiver

One thing to note is that if you annotate a class with @AndroidEntryPoint, then other classes that depend on that class need to be added as well.

The typical example is a Fragment, so you need to annotate all activities that depend on it.

Dagger can show you what @androidEntryPoint does. It automatically generates Componennt for the Android class and adds it to the Application class.

@Inject

The use of @Inject is basically the same as that of Dagger. It can be used to define a constructor or field, and declare that the constructor or field needs to be acquired by dependency.

class UserRepository @Inject constructor(
    private val service: GithubService
) : BaseRepository() {... }Copy the code

@Module

The Hilt Module also needs to add @Module annotations, unlike the Dagger it must also add annotations to the Module using @installin. The purpose is to tell which Android class the module will be used in.

@Binds

The @Sharing annotation tells Hilt which implementation to use when it needs to provide instances of the interface. It’s no different than Dagger, right

@Module
@InstallIn(ActivityComponent::class)
abstract class WelcomeModule {
 
    @Binds
    @IntoMap
    @ViewModelKey(WelcomeVM::class)
    abstract fun bindViewModel(viewModel: WelcomeVM): ViewModel
}
Copy the code

The difference is that @InstallIn is added to the ActivityComponent::class to indicate that the scope of this module is Activity

In fact, the above injection to the ViewModel, which is automatically written for us when we use Hilt, just to show the difference with Dagger. ViewModel injection will be covered later.

@Providers

Provide an instance of FragmentManager, starting with the use of the Dagger

@Module
class MainProviderModule(private val activity: FragmentActivity) {
 
    @Provides
    fun providersFragmentManager(): FragmentManager = activity.supportFragmentManager
}
Copy the code

Compare that to Hilt

@InstallIn(ActivityComponent::class)
@Module
object MainProviderModule {
 
    @Provides
    fun providerFragmentManager(@ActivityContext context: Context) = (context as FragmentActivity).supportFragmentManager
}
Copy the code

The difference is that in Hilt, @providers must be static and constructors cannot take arguments.

The @ActivityContext is a predefined qualifier provided by Hilt that provides a Context from the Activity, along with the @ApplicationContext

Provided components

There are different Android classes associated with the @Installin mentioned earlier, in addition to @ActivityComponent

The corresponding life cycle is as follows

The corresponding scope is also provided

So the default provision of Hilt will greatly improve development efficiency and eliminate many duplicate definitions

ViewModel

Finally, ViewModel injection. If you use Jetpack, it will be injected.

For the Dagger we need to customize a ViewModelFactory and provide the injection method, for example ViewModelFactory is defined in AwesomeGithub’s ComponentBridGet module

@Module
abstract class ViewModelBuilderModule {
 
    @Binds
    abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory } class ViewModelFactory @Inject constructor(private val creators: @JvmSuppressWildcards Map<Class<out ViewModel>, Provider<ViewModel>>) : ViewModelProvider.Factory { override fun <T : ViewModel? > create(modelClass: Class<T>): T { var creator = creators[modelClass]if (creator == null) {
            for ((key, value) in creators) {
                if (modelClass.isAssignableFrom(key)) {
                    creator = value
                }
            }
        }
 
        if (creator == null) {
            throw IllegalArgumentException("Unknown model class: $modelClass")
        }
 
        try {
            @Suppress("UNCHECKED_CAST")
            return creator.get() as T
        } catch (e: Exception) {
            throw RuntimeException()
        }
    }
 
}
Copy the code

The construct instance is injected via @inject, but the Creators need to provide the Map-type creators in the constructor. In this case, you can use @intomap. To match the Map type, you need to define an @mapKey annotation

@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
@Retention(AnnotationRetention.RUNTIME)
@MapKey
annotation class ViewModelKey(val value: KClass<out ViewModel>)
Copy the code

Then use it under the corresponding component, such as matching MainVM

@Module
abstract class MainModule {
 
    @Binds
    @IntoMap
    @ViewModelKey(MainVM::class)
    abstract fun bindViewModel(viewModel: MainVM): ViewModel
 
}
Copy the code

This provides the Map

, MainVM> parameter types, at which point our custom ViewModelFactory can be successfully injected.

For example, BaseDaggerActivity in the Basic module

abstract class BaseDaggerActivity<V : ViewDataBinding, M : BaseVM> : AppCompatActivity() {

    protected lateinit var viewDataBinding: V

    @Inject
    lateinit var viewModelFactory: ViewModelProvider.Factory

    protected val viewModel by lazy { ViewModelProvider(this, viewModelFactory)[getViewModelClass()] }
    ...
}
Copy the code

Of course, don’t forget that MainVM also uses @Inject to declare injection

class MainVM @Inject constructor() : BaseVM() {... }Copy the code

This is the injection method used by the Dagger ViewModel.

Although the custom ViewModelFactory is common, you need to manually define different bindViewModel methods for different viewModels.

With Hilt, you can skip this step, or even write all of the above by hand. All we need to do is just add @ViewModelInject to the constructor of the ViewModel.

For example, with MainVM above, the effect of using Hilt is as follows

class MainVM @ViewModelInject constructor() : BaseVM() {... }Copy the code

Why is Hilt so simple? Let’s not forget the essence of it, which is built on top of Dagger to help reduce unnecessary boilerplate templates and make dependency injection easier for developers.

In Hilt, the above implementation automatically generates it for us, which is why it’s so easy to use.

If you compare the code in the Feat_dagger and Feat_hilt branches on AwesomeGithub, there is significantly less code to use Hilt. For simple Android classes, it’s just a matter of adding a few comments.

The only thing that is not ideal at this point is the use of @Providers. There can be no arguments in the constructor. Converting to Hilt may not be easy if there are already arguments in the Dagger.

Thankfully, Dagger and Hilt can co-exist. So you can use it selectively.

But overall Hilt smells good, and you’ll never regret it if you try it

AwesomeGithub

AwesomeGithub is based on Github client, pure exercise project, support component development, support account password and authentication login. Kotlin language for development, the project architecture is based on JetPack&DataBinding MVVM; Popular open source technologies such as Arouter, Retrofit, Coroutine, Glide, Dagger and Hilt are used in the project.

In addition to the Android native version, there is a cross-platform version of Flutter based on Flutter_Github.

If you like my article mode, or are interested in my next article, you can follow my wechat official account: [Android supply station]

Or scan the qr code below to establish effective communication with me and receive relevant technical push more conveniently.