Dagger Hilt
There are a number of DI frameworks available on Android — ButterKnife for control injection, Koin for Kotlin, etc. — but Dagger is the only DI solution officially recognized by Google.
Dagger was originally developed by Square and later upgraded to Dagger2 by Google Fork, making it an official RECOMMENDED DI best practice for Android. Dagger implements the JSR-330 specification fairly well and, while powerful, does not work well for Android projects. Google followed up with dagger-Android (and dagger-Android-support), which attempted to reduce the cost of using dagger in Android development with new annotations, but the effect was not satisfactory. Hence the Launch of the Dagger Hilt on Android Dev Summit 2019 and the launch of alpha this year
Developer.android.com/training/de…
Some articles say Hilt is a replacement for Dagger, but more accurately it is used to replace Dagger – Android. Hilt, with its very second name, is designed to help beginners use the Dagger better in Android development. It proxies for a lot of complex initial configurations, greatly reducing development costs and avoiding the backlash from “Dagger”.
Let’s take a look at the basic use of the Dagger Hilt with a simple example.
Github.com/vitaviva/Da…
Gradle
// build.gradle
buildscript {
dependencies {
classpath 'com. Google. Dagger hilt - android - gradle - plugin: 2.28 alpha'}}Copy the code
// app/build.gradle
apply plugin: 'dagger.hilt.android.plugin'
apply plugin: 'kotlin-kapt'
dependencies {
implementation 'com. Google. Dagger hilt - android: 2.28 alpha'
kapt 'com. Google. Dagger hilt - android - the compiler: 2.28 alpha'
}
Copy the code
Application
With a dagger -Andorid, you define an app-level Component and declare its dependent Module
@Singleton
@Component(modules = [ AndroidInjectionModule::class, ActivityModule::class, FragmentModule::class, ViewModelModule::class])
interface AppComponent : AndroidInjector<DaggerApplication> {
@Component.Factory
interface Factory {
fun create(@BindsInstance application: Application): AppComponent
}
}
Copy the code
Then either inherit the DaggerApplication or implement the HasAndroidInjector interface to create the Component.
class MyApplication : DaggerApplication() {
override fun applicationInjector(a) = DaggerAppComponent.factory().create(this)}Copy the code
Now to use Hilt, you just need a comment @hiltAndroidApp to do all of this.
@HiltAndroidApp
class App : Application() {}Copy the code
Component & Module
Dagger -Andorid needs to define AppComponent with @Component as above, so how does Hilt create AppComponent and determine which Module it depends on? .
Hilt already presets Components for various Android components
Hilt’s Module does not need to be declared in Component, but instead uses @installin to reverse declare Component when defining Modle
// ApplicationModule.kt
@Module
@InstallIn(ApplicationComponent::class)
class ApplicationModule {
@Singleton
@Provides
fun provide(a): String {
return hashCode().toString()
}
}
Copy the code
//ActivityModule.kt
@Module
@InstallIn(ActivityComponent::class)
class ActivityModule {
@ActivityScope
@Provides
fun provide(a): String {
return hashCode().toString()
}
}
Copy the code
In the example, the Provide type is String, so custom annotations are used to distinguish it from the @qualifier type
@Qualifier
@Retention(AnnotationRetention.RUNTIME)
internal annotation class AppScope
@Qualifier
@Retention(AnnotationRetention.RUNTIME)
internal annotation class ActivityScope
Copy the code
Activity & Fragment
The Provide side of DI is implemented simply. Next, look at Inject side — mainly for activities and fragments injection. dagger-android
through@ContributesAndroidInjector
Help us generateSubComponent
; Through inheritanceDaggerAppCompatActivity
The Activity can be injected automatically at onCreate. Although saved some template code, but @ ContributesAndroidInjector appeared to some extent and become a new template code.
@Module
abstract class ActivityModule {
@ActivityScope
@ContributesAndroidInjector(modules = [FragmentModule::class])
internal abstract fun contributeMainActivity(a): MainActivity
@ActivityScope
@ContributesAndroidInjector
internal abstract fun contributeSecondActivity(a): SecondActivity
}
Copy the code
Now that Hilt has preset components, it no longer relies on creating subcomponents. (Specifically, it does not require developers to customize subcomponents, but it still creates subcomponents from preset components. For example, generate Hilt_ActivityComponent for ActivityComponent, again verifying that Hilt is only used to replace the dagger android, the underlying mechanism still depends on the dagger), You only need an annotation @AndroidEntryPoint to inject components such as Activity.
AndroidEntryPoint uses bytecode pegs to change the inheritance structure of the target class object at compile time (e.g., insert Hilt_MainActivity as the parent between MainActivity and AppComponentActivity). Create the SubComponent and inject the target in real time
Component | Description | Created at | Destroyed at |
---|---|---|---|
ApplicationComponent | Provide dependencies for your App | Application#onCreate() | Application#onDestroy() |
ActivityComponent | Provide dependencies for the Activity | Activity#onCreate() | Activity#onDestroy() |
ActivityRetainedComponent | Retained as its name implies, its life cycle is longer and it can not be reconstructed due to screen rotation and other factors. In fact, it is implemented with the help of ViewModel | Activity#onCreate() | Activity#onDestroy() |
FragmentComponent | Provide dependencies for fragments | Fragment#onAttach() | Fragment#onDestroy() |
ViewComponent | Provides a dependency to the View, which is injected in the constructor | View#super() | View destroyed |
ViewWithFragmentComponent | Provide dependencies for views in fragments | View#super() | View destroyed |
ServiceComponent | Provide dependencies for services | Service#onCreate() | Service#onDestroy() |
Like SubComponent, the Scope of a preset Component is inherited
// MainActivity.kt
@AndroidEntryPoint
class MainActivity : AppCompatActivity(R.layout.activity_main) {
@AppScope
@Inject
lateinit var appHash: String
@ActivityScope
@Inject
lateinit var activityHash: String
override fun onCreate(savedInstanceState: Bundle?). {
super.onCreate(savedInstanceState)
Log.v(TAG, "app : $appHash")
Log.v(TAG, "activity : $activityHash")}}Copy the code
// MainFirstFragment.kt
@AndroidEntryPoint
class FirstFragment : Fragment(R.layout.fragment_first) {
@AppScope
@Inject
lateinit var appHash: String
@ActivityScope
@Inject
lateinit var activityHash: String
@FragmentScope
@Inject
lateinit var fragmentHash: String
override fun onViewCreated(view: View, savedInstanceState: Bundle?). {
super.onViewCreated(view, savedInstanceState)
Log.d(TAG, "app : $appHash")
Log.d(TAG, "activity : $activityHash")
Log.d(TAG, "fragment : $fragmentHash")}}Copy the code
ViewModel
With the spread of the MVVM architecture, viewModels have become standard on Android projects. But ViewModel injection has always been tedious. On the one hand, viewModels are mostly provided by ViewModelFactory, so we can’t customize the Provider. On the other hand, Factory mostly creates viewModels by reflection, so constructor injection is not possible. The ViewModel’s recently added SavedStateHandle is more difficult to handle.
So far, @Intomap has been used to implement ViewModel injection along with the definition of ViewModelFactory.
@Module
abstract class ViewModelModule {
@Binds
@IntoMap
@ViewModelKey(ActivityViewModel::class)
abstract fun bindActivityViewModel(viewModel: ActivityViewModel): ViewModel
@Binds
@IntoMap
@ViewModelKey(FragmentViewModel::class)
abstract fun bindFragmentViewModel(viewModel: FragmentViewModel): ViewModel
}
Copy the code
@Singleton
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 {
val found = creators.entries.find { modelClass.isAssignableFrom(it.key) }
valcreator = found? .value ? :throw IllegalArgumentException("unknown model class " + modelClass)
try {
@Suppress("UNCHECKED_CAST")
return creator.get(a)as T
} catch (e: Exception) {
throw RuntimeException(e)
}
}
}
Copy the code
Hilt provides a library extension for androidx that addresses the problem of ViewModel injection. For more details on ViewModel injection, please refer to my other article on dependency injection and implementation principles for ViewModel
Gradle
First, configure the androidx repository address
// build.gradle
allprojects {
repositories {
maven {
url "https://androidx.dev/snapshots/builds/6543454/artifacts/repository/"}}}Copy the code
// app/build.gradle
dependencies {
implementation 'androidx. Lifecycle: lifecycle - viewmodel - KTX: 2.2.0'
implementation 'androidx. Lifecycle: lifecycle - viewmodel - savedstate: 2.2.0'
implementation 'androidx. Hilt: hilt - common: 1.0.0 - the SNAPSHOT'
implementation 'androidx. Hilt: hilt - lifecycle - viewmodel: 1.0.0 - the SNAPSHOT'
kapt 'androidx. Hilt: hilt - compiler: 1.0.0 - the SNAPSHOT'
}
Copy the code
ViewModel
Constructor injection is done via @ViewModelInject without constructing any Factories. SavedStateHandle annotated with @assisted
class ActivityViewModel @ViewModelInject constructor(
private val repository: Repository,
@Assisted private val savedState: SavedStateHandle
) : ViewModel() {
val repository(): String = repository.toString()
}
Copy the code
@singletonMock A remote Repo. This is written the same way so far
@Singleton
class Repository @Inject constructor() {
fun getSomething(a): Something
}
Copy the code
Activity & Fragment
Activities and fragments get the ViewModel as usual from KTX viewModels, activityViewModels
// MainActivity.kt
@AndroidEntryPoint
class MainActivity : AppCompatActivity(R.layout.activity_main) {
private val viewModel by viewModels<ActivityViewModel>()
override fun onCreate(savedInstanceState: Bundle?). {
super.onCreate(savedInstanceState)
Log.v(TAG, "repository : $repository")
Log.v(TAG, "activity vm : $viewModel")
Log.v(TAG, "activity vm repo : ${viewModel.repository}")}}Copy the code
FirstFragment.kt
@AndroidEntryPoint
class FirstFragment : Fragment(R.layout.fragment_first) {
private val activityViewModel by activityViewModels<ActivityViewModel>()
private val fragmentViewModel by viewModels<FragmentViewModel>()
override fun onViewCreated(view: View, savedInstanceState: Bundle?). {
super.onViewCreated(view, savedInstanceState)
Log.d(TAG, "activity vm: $activityViewModel")
Log.d(TAG, "fragment vm: $fragmentViewModel")
Log.d(TAG, "activity vm repo: ${activityViewModel.repository}")
Log.d(TAG, "fragment vm repo: ${fragmentViewModel.repository}")}}Copy the code
Repository exists as a global Singleton because it belongs to @singleton. The ViewModel exists as multiple instances depending on the location of by
Summary
As you can see from the example, Hilt reduces a lot of template code relative to the dagger android, but it also reduces flexibility. For example, if you use the preset ActivityComponent, you can provide the same injection for all activities. Code isolation is not as refined as custom Component. But this sacrifice is nothing compared to the improvement of the development experience. Hilt as the official recommended DI library, the future prospects are very promising
The Sample repo:
Github.com/vitaviva/Da…
More reference
Dagger Hilt-viewModel dependency injection and implementation principle