2

I have a class that is dagger injected via the constructor. However I now need to add an argument to this constructor that is not provided via injection and is instead a run time argument.

In my previous job, we rolled our own DI so I am not up to speed with all the "magic" annotations that dagger offers yet. I figured it should be simple to add an argument to a constructor and still have dagger inject the remaining values (as it was very simple to do with the aforementioned "roll your own DI" solution I have implemented before).

However, it looks like this is not possible with dagger (i.e. assisted injection). So I have been reading on how to solve this issue but have become completely stumped.

Here is the class that I am currently (successfully) injecting ServiceA into:

class Foo @Inject constructor(private val serviceA: ServiceA) {
    ...
}

What I would like to do is add another argument to this constructor that will be provided at run time. In this case a simple flag to determine some behaviour in the class. And, given that Dagger doesn't support assisted injection, I need to remove injection from Foo and instead create a FooFactory to handle creation of Foo objects. This factory will have ServiceA injected into its constructor and will provide a method to construct an instance of Foo taking the boolean. I will then end up with a Foo class that looks like:

class Foo(private val serviceA: ServiceA, myNewArgument: Boolean) {
    ...
}

And a FooFactory that looks like:

@Singleton
class FooFactory @Inject constructor(private val serviceA: ServiceA) {
    fun createFoo(myNewArgument: Boolean) {
        return Foo(serviceA, myNewArgument)
    }
}

And, although this is a complete mess to just get an extra constructor arg, it does the job.

The problem I am facing, is that my Foo class is actually an AndroidViewModel, and will need to be constructed through the ViewModelProvider.Factory contract, which has a create method which is invoked by the SDK to create the view model. You override this method to create an instance of the view model but the method has no parameters, so there is no way to propagate the flag into the view model through this method.

So the only way for me to get the flag propagated to the view models constructor is by having the factory itself take the flag as an argument to it's constructor, which, because dagger does not support assisted injection, is not possible.

So, instead I am planning to make dagger manually inject my dependencies into the FooFactory at initialization time. This is where I am stuck, I cannot figure out how on earth to get dagger to manually inject dependencies into my class.

My FooFactory now looks like:

class FooFactory(private val myNewArgument: Boolean) : ViewModelProvider.Factory {

    init {
        // I need to trigger injection here... how though???
    }

    @Inject
    lateinit var serviceA: ServiceA

    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return Foo(
                serviceA,
                myNewArgument
        ) as T
    }
}

So, somehow in the init block of the factory, I need to ask dagger to inject the fields annotated with @Inject. But I have no idea how to do this, I have tried following the answers at the following questions and tutorials:

https://proandroiddev.com/from-dagger-components-to-manual-dependency-injection-110015abe6e0

Dagger 2 - injecting non Android classes

Dagger 2 injection in non Activity Java class

None of these seem to work for my use case and I'm starting to lose it. Could anyone point me in the right direction? Is this dagger framework massively over engineered/complicated for not much benefit (this is the conclusion I am coming to at this point, all I want to do is achieve DI for testing purposes, I don't want to have to write factories so I can add an extra argument to a constructor)...

Thomas Cook
  • 4,371
  • 2
  • 25
  • 42
  • 1
    Have you seen Square's [AssistedInject](https://github.com/square/AssistedInject) library? It basically uses factories the way you've already tried, but generates most of the code and integrates with Dagger 2 quite neatly. [Here](https://proandroiddev.com/saving-ui-state-with-viewmodel-savedstate-and-dagger-f77bcaeb8b08) is an example of how to use it with `ViewModel`. – jsamol Dec 18 '19 at 12:17
  • Cheers, I'll check it out – Thomas Cook Dec 18 '19 at 13:05

0 Answers0