B
Appendix B: Assisted Injection
Written by Massimo Carli
Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as
text.You can unlock the rest of this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.
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:
- What assisted injection is.
- 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:
-
MyService
as an implementation of theService
interface, and you annotate its primary constructor with@Inject
. This tells Dagger how to create an instance ofMyService
to use anywhere you need an object of typeService
. - The dependencies of
MyService
as primary constructor parameters. In this case,MyService
needs objects of typesDependency1
,Dependency2
andDependency3
.
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.
hilt_version = "2.31.2-alpha"
class FunNumberServiceImpl @Inject constructor( // 1
private val numberGenerator: NumberGenerator, // 2
private val funNumberEndpoint: FunNumberEndpoint // 2
) : FunNumberService {
// ...
}
// ...
@Binds
@ActivityScoped
fun bindFunNumberService( // HERE
impl: FunNumberServiceImpl
): FunNumberService
// ...
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.
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.
class FunNumberServiceImpl @AssistedInject constructor( // HERE
private val numberGenerator: NumberGenerator,
private val funNumberEndpoint: FunNumberEndpoint
) : FunNumberService {
// ...
}
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.
class FunNumberServiceImpl @AssistedInject constructor(
@Assisted private val numberGenerator: NumberGenerator, // HERE
private val funNumberEndpoint: FunNumberEndpoint
) : FunNumberService {
// ...
}
Using @AssistedFactory
So far, you’ve told Dagger that:
@AssistedFactory // 1
interface FunNumberServiceFactory { // 2
fun create(
numberGenerator: NumberGenerator // 3
): FunNumberServiceImpl // 4
}
@Module(includes = [
NavigationModule::class
])
@InstallIn(ActivityComponent::class)
object ActivityModule
Using FunNumberServiceFactory in FunNumberFragment
You now need to inject FunNumberServiceFactory
where you need a FunNumberService
.
@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()
}
}
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:
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
.