Chapters

Hide chapters

Dagger by Tutorials

First Edition · Android 11 · Kotlin 1.4 · AS 4.1

B. Appendix B: Assisted Injection
Written by Massimo Carli

Dagger and Hilt are libraries in continuous evolution. Google and Square, with the help of the open-source community, keep improving them, both by creating new features and by improving the performance of the existing ones.

One of Dagger’s new improvements is assisted injection, which Google added in version 2.31. In this appendix, you’ll learn:

  1. What assisted injection is.
  2. How to implement it with @Assisted, @AssistedInject and @AssistedFactory.

To do this, you’ll work on the RandomFunNumber app.

What is assisted injection?

In this book, you learned all about dependency injection. You saw several examples of how dependency injection can improve the maintainability and testability of your code. You also learned that constructor injection is the best type of injection because it allows you to provide dependencies at the exact moment you create an instance of an object.

It’s not unusual to see code like this:

class MyService @Inject constructor( // 1 
	private val dep1: Dependency1, // 2
	private val dep2: Dependency2, // 2
	private val dep3: Dependency3  // 2
) : Service

Here, you define:

  1. MyService as an implementation of the Service interface, and you annotate its primary constructor with @Inject. This tells Dagger how to create an instance of MyService to use anywhere you need an object of type Service.
  2. The dependencies of MyService as primary constructor parameters. In this case, MyService needs objects of types Dependency1, Dependency2 and Dependency3.

By also telling Dagger how to provide objects of type Dependency1, Dependency2 and Dependency3, you know Dagger will create the instance of MyService for you every time you need a Service.

This is very cool, but sometimes you need something a bit different. To understand what, return to RandomFunNumber to see a practical example.

An example of constructor injection

Open the RandomFunNumber project from the starter folder of the materials for this appendix. This is a simplified version of the project you used in Chapter 19, “Testing With Hilt”, with an important difference.

Figure 21.1 — The RandomFunNumber app
Figure 21.1 — The RandomFunNumber app

In versions.gradle, the version of the Hilt library is now 2.31.2-alpha. It depends on version 2.31.2 of Dagger:

hilt_version = "2.31.2-alpha"

This is important because assisted injection was introduced in Dagger 2.31, so you need to use at least that version.

Now, open FunNumberServiceImpl.kt in the business package and look at the following code:

class FunNumberServiceImpl @Inject constructor( // 1
    private val numberGenerator: NumberGenerator, // 2
    private val funNumberEndpoint: FunNumberEndpoint // 2
) : FunNumberService {
  // ...
}

This is an example of constructor injection, where you:

  1. Use @Inject to tell Dagger to use the primary constructor to create the instances.
  2. Define the dependencies on NumberGenerator and FunNumberEndpoint.

Next, open ActivityModule.kt in di to find the following definition:

  // ...
    @Binds
    @ActivityScoped
    fun bindFunNumberService( // HERE
        impl: FunNumberServiceImpl 
    ): FunNumberService
  // ...    

This code just says that every time you need to inject an object of the type FunNumberService, Dagger will create an instance of FunNumberServiceImpl to handle the dependencies you saw earlier.

With this configuration, Dagger provides all the dependencies for you. But what if you want to provide a different dependency every time you need a FunNumberServiceImpl? That’s where assisted injection comes in handy.

Providing dependencies with assisted injection

Suppose you want to provide a different NumberGenerator implementation every time you need a FunNumberServiceImpl. One way to achieve this is to use a custom qualifier.

However, while that would work, it would be quite verbose. Assisted injection is an elegant alternative.

To use this, you need:

  1. @AssistedInject in place of @Inject for FunNumberServiceImpl.
  2. @Assisted to tell Dagger which dependency you’ll provide it.
  3. @AssistedFactory to define the Factory that binds type FunNumberService.
  4. To inject the Factory wherever you need a FunNumberService and provide the proper dependency.

Now, you’re ready to code along and add assisted injection to RandomFunNumber.

Replacing @Inject with @AssistedInject

For your first step, you’ll inform Dagger that you want to use assisted injection, and will therefore provide some of the dependencies you need for a specific binding.

To do this, open FunNumberServiceImpl.kt in business and apply the following change:

class FunNumberServiceImpl @AssistedInject constructor( // HERE
    private val numberGenerator: NumberGenerator,
    private val funNumberEndpoint: FunNumberEndpoint
) : FunNumberService {
  // ...
}

This code replaces @Inject with @AssistedInject. This tells Dagger that you’ll explicitly provide some of the dependencies you define as primary constructor parameters.

However, the code above doesn’t tell Dagger which dependencies you’ll provide. For that, you need @Assisted.

Using @Assisted

You just learned how to tell Dagger that you’ll handle some of FunNumberServiceImpl’s dependencies. Now, you need to declare which dependencies you’ll provide.

In the same FunNumberServiceImpl.kt in business, add the following definition:

class FunNumberServiceImpl @AssistedInject constructor(
    @Assisted private val numberGenerator: NumberGenerator, // HERE
    private val funNumberEndpoint: FunNumberEndpoint 
) : FunNumberService {
  // ...
}

When you add the import for @Assisted, you’ll probably see two different options. That’s because there’s an @Assisted in the dagger.assisted package and another in androidx.hilt. The source code in the latter has a comment saying that it’ll be replaced with the former. Therefore, the one in dagger.assisted is the right one to import in your class.

In this code, you use @Assisted for the primary constructor parameter of type NumberGenerator. With this, you tell Dagger that, when it’s time to create the object to bind to FunNumberService, you’ll explicitly provide the dependency of type NumberGenerator.

But how can you do this? You need a Factory.

Using @AssistedFactory

So far, you’ve told Dagger that:

  1. You want to do part of its job by providing some of the dependencies for FunNumberService’s binding.
  2. You’ll provide the dependency for the parameter of type NumberGenerator.

Now, you need a way to pass the binding: a Factory.

To add this, create a new file named FunNumberServiceFactory.kt in business and add the following code:

@AssistedFactory // 1
interface FunNumberServiceFactory { // 2

  fun create(
      numberGenerator: NumberGenerator // 3
  ): FunNumberServiceImpl // 4
}

In this code, you:

  1. Use @AssistedFactory to tell Dagger to generate this interface of the Factory for you and create the object to bind FunNumberServiceImpl.
  2. Define Factory as an interface.
  3. Need a create operation that has the @Assisted dependencies as its parameters. Earlier, you told Dagger that you’ll provide the dependency for NumberGenerator. This parameter is how you do that.
  4. Return an object of type FunNumberServiceImpl. This last point is fundamental. The returning type is not the type of the FunNumberService abstraction, but the type of @AssistedInject, which is its implementation.

Because of the last point, you can open ActivityModule.kt in di and change it to this:

@Module(includes = [
  NavigationModule::class
])
@InstallIn(ActivityComponent::class)
object ActivityModule

In this code, you deleted the bindings for:

  1. FunNumberService, because you’ll provide it through FunNumberServiceFactory.
  2. NumberGenerator because you’ll provide this information explicitly — as you’ll see in a bit.

Now that you’ve defined FunNumberServiceFactory, Dagger will also create the binding for you. This means you don’t need to add this definition to any @Module. You can now simply @Inject the FunNumberServiceFactory where you need it — in this case, in FunNumberViewModel.

Using FunNumberServiceFactory in FunNumberFragment

You now need to inject FunNumberServiceFactory where you need a FunNumberService.

Do this by opening FunNumberFragment.kt in ui.displaynumber and apply the following changes:

@AndroidEntryPoint
class FunNumberFragment : Fragment() {

  private lateinit var funNumberTextView: TextView
  private lateinit var funFactTextView: TextView

  @Inject
  lateinit var funNumberServiceFactory: FunNumberServiceFactory // 1
  private lateinit var funNumberService: FunNumberService // 2

  override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
    val ctx = container?.context ?: IllegalStateException("Context not available")
    funNumberService = funNumberServiceFactory.create(object : NumberGenerator { // 3
      override fun randomNumber(): Int = 28
    })
    return LayoutInflater.from(ctx as Context).inflate(R.layout.fragment_show_number, container, false).apply {
		// ...       
    }
  }

  override fun onStop() {
    funNumberService.stop()
    super.onStop()
  }
}

In this code, you:

  1. Use @Inject to get the reference to FunNumberServiceFactory into the funNumberServiceFactory property.
  2. Define funNumberService to store the reference to the FunNumberService instance you’ll — theoretically — create later.
  3. Invoke create() on the funNumberServiceFactory to create the FunNumberServiceImpl instance, passing the reference to a new NumberGenerator implementation you’ll create locally.

Note: In this code, you’re only practicing how to use assisted injection. The instance of the NumberGenerator implementation you create here doesn’t matter.

Now, run the app. Everything works as expected:

Figure 21.2 — The RandomFunNumber app
Figure 21.2 — The RandomFunNumber app

Limitations to assisted injection in Dagger

Congratulations! You’ve added assisted injection to the RandomFunNumber app. This is a new feature in Dagger, so be aware that following versions of the library might include improvements. At the moment, it has some limitations, including:

  1. FunNumberServiceFactory defines create(), which has FunNumberServiceImpl as return type. That’s because it must be the type of the class whose primary constructor you annotated with @AssistedInject. It would be nice to have the FunNumberService abstraction as the return type instead.
  2. At the moment, @AssistedInject cannot use @Scopes.

Key points

  • Dagger has offered assisted injection since version 2.31.0.
  • @AssistedInject allows you to tag the primary constructor with @Assisted parameters.
  • To create an instance of @AssistedInject, you need an @AssistedFactory.
Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2026 Kodeco Inc.