Assisted Injection With Dagger and Hilt
Learn what assisted injection is used for, how it works, and how you can add it to your app with Dagger’s new built-in support for the feature. By Massimo Carli.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Assisted Injection With Dagger and Hilt
30 mins
- Getting Started
- AssistedGallery App Architecture
- The ImageLoader Class
- Managing Dependencies With Constructor Injection
- Implementing the loadImage Function
- Using the ImageLoader Class
- Injecting Only What You Need
- What About the Other Parameters?
- Using Assisted Injection With AutoFactory
- Configuring AutoFactory
- Using AutoFactory in the AssistedGallery App
- Preparing a Class for Assisted Injection
- A Look at the Generated Code
- Using the Generated Factory
- Assisted Injection with Dagger 2.31+
- Updating the Dependencies
- Using @AssistedInject and @Assisted
- Creating a Factory with @AssistedFactory
- Assisted Injection on the Use Site
- Using Default Parameters With Dagger Assisted Injection
- Assisted Injection and ViewModels
- Adding the Required Dependencies
- Implementing the ViewModel
- Creating an @AssistedFactory for the ViewModel
- Assisted Injecting the ViewModel
- Where to Go From Here?
A Look at the Generated Code
To understand how to use ImageLoader
, you need to build the app and look at the generated code in the build/generated/source/kapt/debug directory, as seen in the following picture:
If you open ImageLoaderFactory.java, you’ll see the following:
@Generated( // 1
value = "com.google.auto.factory.processor.AutoFactoryProcessor",
comments = "https://github.com/google/auto/tree/master/factory"
)
public final class ImageLoaderFactory {
private final Provider<BitmapFetcher> bitmapFetcherProvider; // 2
private final Provider<CoroutineDispatcher> bgDispatcherProvider; // 2
private final Provider<CoroutineDispatcher> uiDispatcherProvider; // 2
@Inject // 4
public ImageLoaderFactory(
Provider<BitmapFetcher> bitmapFetcherProvider, // 3
@Schedulers.IO Provider<CoroutineDispatcher> bgDispatcherProvider, // 3
@Schedulers.Main Provider<CoroutineDispatcher> uiDispatcherProvider) { // 3
this.bitmapFetcherProvider = checkNotNull(bitmapFetcherProvider, 1);
this.bgDispatcherProvider = checkNotNull(bgDispatcherProvider, 2);
this.uiDispatcherProvider = checkNotNull(uiDispatcherProvider, 3);
}
public ImageLoader create(int loadingDrawableId, ImageFilter imageFilter) { // 5
return new ImageLoader(
checkNotNull(bitmapFetcherProvider.get(), 1),
checkNotNull(bgDispatcherProvider.get(), 2),
checkNotNull(uiDispatcherProvider.get(), 3),
loadingDrawableId,
checkNotNull(imageFilter, 5));
}
// ...
}
This Java code generated by AutoFactory contains many interesting things:
- The
@Generated
annotations provide metadata about what generated the file. - A final field for each constructor parameter you annotated with
@Provided
.These fields are initialized by using the constructor parameters of the factory. - The
@Inject
annotation on the constructor means that Dagger will be able to create an instance ofImageLoaderFactory
. -
AutoFactory generates
create()
with the parameters you didn’t mark as@Provided
. The implementation is quite simple; it creates anImageLoader
instance using both the values from the constructor and the ones passed as parameters ofcreate()
itself.
It’s now time to use the generated ImageLoaderFactory
in the MainActivity
.
Using the Generated Factory
Open MainActivity.kt in the ui package and apply the following changes:
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
@Inject
lateinit var imageLoaderFactory: ImageLoaderFactory // 1
// ...
fun loadImage() {
lifecycleScope.launch {
imageLoaderFactory
.create( // 2
R.drawable.loading_animation_drawable,
GrayScaleImageFilter()
).loadImage(imageUrlStrategy(), mainImage)
}
}
}
With this code, you:
- Inject the
ImageLoaderFactory
in place of theImageLoader
. - Invoke
create()
, passing in aDrawable
and anImageFilter
to create an instance ofImageLoader
. In this example, you’re using a GrayScaleImageFilter as the ImageFilter implementation.
ImageLoaderFactory
, you can delete ImageLoaderModule.kt in the di package.Build and run the app to see the new grayscale filter in action.
This means Dagger provides some of the dependencies through Factory, and you provide the remaining dependencies as parameters for create()
.
Drawable
to display while loading and ImageFilter
used to have default values. Where did those go? Java doesn’t have a concept of default values for parameters, so the annotation processor doesn’t know they exist. You might think that using @JvmOverloads
would generate different overloads for create()
, but unfortunately, this isn’t yet supported.
Assisted Injection with Dagger 2.31+
If you’re using Dagger with version 2.31 or later, you can benefit from assisted injection without any additional dependencies. As you’ll see very soon, you can achieve the same result you got with @AutoFactory by using different annotations.
To migrate to assisted injection with Dagger, you need to:
- Remove dependencies to AutoFactory and update the version of Dagger/Hilt.
- Use
@AssistedInject
and@Assisted
instead of@AutoFactory
and@Provided
, respectively. - Define a Factory implementation with the
@AssistedFactory
annotation.
It’s time to migrate ImageLoader
to using assisted injection with Dagger.
Updating the Dependencies
As the first step, open build.gradle for the app module and remove the definitions you added earlier:
// START REMOVE
implementation 'com.google.auto.factory:auto-factory:1.0-beta5@jar'
kapt 'com.google.auto.factory:auto-factory:1.0-beta5'
compileOnly 'javax.annotation:jsr250-api:1.0'
// END REMOVE
After that, upgrade the version for Hilt. At the time of writing, this is 2.33-beta. You can also check MavenCentral for the latest available version.
To update the version for Hilt, change the value for hilt_android_version. Open the project-level build.gradle file and update the version:
buildscript {
ext.kotlin_version = "1.4.31"
ext.hilt_android_version = "2.33-beta" // Update this value
repositories {
google()
mavenCentral()
}
// ...
}
// ...
Before proceeding, open ApplicationModule.kt and replace both references to ApplicationComponent::class with SingletonComponent::class, as this was renamed in newer Dagger versions:
@Module(includes = arrayOf(Bindings::class))
@InstallIn(SingletonComponent::class) // Check this line
object ApplicationModule {
// ...
@Module
@InstallIn(SingletonComponent::class) // Check this line
interface Bindings {
// ...
}
}
Your code should look like what’s shown above.
Using @AssistedInject and @Assisted
Now you need to inform Dagger about the classes that use assisted injection, as well as about what parameters Dagger should provide. Open ImageLoader.kt in the bitmap package and change its constructor, like so:
// 1
class ImageLoader @AssistedInject constructor( // 2
private val bitmapFetcher: BitmapFetcher,
@Dispatchers.IO private val bgDispatcher: CoroutineDispatcher,
@Dispatchers.Main private val uiDispatcher: CoroutineDispatcher,
@Assisted
@DrawableRes private val loadingDrawableId: Int = R.drawable.loading_animation_drawable, // 3
@Assisted
private val imageFilter: ImageFilter = NoOpImageFilter // 3
) {
// ...
}
With this code, you:
- Removed the
@AutoFactory
annotation, which is no longer needed. - Annotated the primary constructor with
@AssistedInject
. - Removed the
@Provided
annotations and added@Assisted
annotations instead for the constructor parameters you’re going to provide.
Note how earlier, you used @Provided
to mark the parameters provided by Dagger. Now you’re doing the opposite: using @Assisted
for the parameters you’re going to provide.
If you try to build and run the project now, you’ll get some errors. This is because assisted injection with Dagger requires one more step to be complete.
Creating a Factory with @AssistedFactory
Tell Dagger what the factory method should look like. In the di package, create a new file called ImageLoaderFactory.kt with the following code:
@AssistedFactory // 1
interface ImageLoaderFactory {
fun createImageLoader( // 2
@DrawableRes loadingDrawableId: Int = R.drawable.loading_animation_drawable,
imageFilter: ImageFilter = NoOpImageFilter
): ImageLoader // 3
}
In this code, you:
- Create
ImageLoaderFactory
and annotate it with@AssistedFactory
. - Define
createImageLoader()
with the parameters you previously set as@Assisted
in the constructor ofImageLoader
. Note that you can name this method freely — it could also be called create(). - Specify
ImageLoader
as the return type.
If you now build the app, the Hilt annotation processor will generate the code for the ImageLoaderFactory
implementation. The build will fail though, as you still need to integrate the new code in the MainActivity.