I am using Dagger-Hilt for Dependency Injection, and I am stuck with not knowing how to do field injection inside an abstract class.
// #ViewModelScoped
abstract class BaseUseCase<Params, Return>{
// lateinit var not initiazlied. Cannot be injected
#Inject
lateinit var errorHandler: ErrorHandler
fun execute(#nullable params: Params?=null): Flow<DataState<Return>> = flow {
emit(Datastate.Loading)
emit(executeRealization(params))
...
}.catch{ e->
when(e){
...
is Exception -> {
...
errorHandler.handleError(e.message ?: "Unknown Error")
}
}
}
protected abstract fun executeRealization(#Nullable params: Params?=null): DataState<Return>
}
[DI package]
I provided "ErrorHandler" as a singleton using dagger-hilt (AppModule.kt)
Usecases which extend above BaseUseCase are all written for dagger-hilt (UseCaseModule.kt)
I tried providing or binding BaseUseCase class using dagger-hilt such as BaseUseCaseModule.kt, however since it has type parameters, it cannot be binded and also provided.
Currently i cannot inject errorHandler inside BaseUseCase class, so just written ErrorHandler 'object' and using it statically. (e.g. Object ErrorHandler {})
Question
How to do field injection inside abstract class?
Or Am i missing something?
How to do field injection inside an abstract class?
This is currently not supported.
You can consider refactoring your code in these two approaches.
First Approach
Move the exception/error handling up the chain towards the UI, this would include the approach of ViewModel.
With this, you can constructor inject your error handler, then execute your UseCase and wrap the handler around it.
Let's look at a possible solution, in the sketch, we'll utilize clean architecture approach;
ViewModel.kt
#HiltViewModel
class YourViewModel #Inject constructor(private val errorHandler: ErrorHandler, private val useCase : SpecificUseCase) : ViewModel(){
suspend fun realizationFunction(params : Params?=null) : Flow<DataState<Return>> = flow {
emit(Datastate.Loading)
try{
emit(useCase(params))
}catch(exception : Exception){
errorHandler.handleError(e.message ?: "Unknown Error")
}
}
}
On your specific useCase, I do recommend you use repository pattern to execute your functions in order to separate concerns instead of executing your functions inside the use case.
Second Approach
This approach involves taking the error handler deeper into the chain and constructor injecting your error handler in your repository implementation.
This would give you the chance to run the particular function/service calls inside a try/catch and handle your error in there.
The con of this second approach may include the challenge of returning the error result, but incorporating a resource class will make it seamless - seems like you have one already, DataState.
class YourRepositoryImpl(private val errorHandler: ErrorHandler) : YourRepositoryInterface {
override suspend fun doSomething(params : Params?) : Flow<DataState<Return>> {
//call your function and use the error handler
}
}
This gives you cleaner code and better error handling.
Or Am I missing something?
You may be interested in reading much about app architecture and separation of concerns.
Related
This is the situation:
I'm using Compose, Hilt, Navigation and ViewModel. I'm trying to get an instance of my ViewModel within a Composable Screen via Hilt:
#Composable
fun HomeScreen(
modifier: Modifier = Modifier,
homeViewModel: HomeViewModel = viewModel()
) {
...
}
#HiltViewModel
class HomeViewModel #Inject constructor(
private val updateCaptureUseCase: UpdateCaptureUseCase
) : ViewModel() {
...
}
class UpdateCaptureUseCase #Inject constructor(private val captureRepository: CaptureRepository) {
...
}
I get an instance of CaptureRepository by defining it inside a Module:
#Module
#InstallIn(ViewModelComponent::class)
abstract class CaptureModule {
#Binds
abstract fun bindCaptureLocalDataSource(
captureLocalDataSourceImpl: CaptureLocalDataSourceImpl
): CaptureLocalDataSource
#Binds
abstract fun bindCaptureRepository(
captureRepositoryImpl: CaptureRepositoryImpl
): CaptureRepository
}
The problem is that CaptureModule appears in Android Studio as if it had no usages.
I can build and run the app with no problems, but when it is supposed to show HomeScreen it crashes. What stresses me out and makes it hard to figure out a solution is that there are no errors in the Run tab nor the Logcat.
If I remove updateCaptureUseCase from the constructor of HomeViewModel, then the app works correctly and is able to reach HomeScreen without errors. Since updateCaptureUseCase depends on CaptureRepository and it is being defined in CaptureModule, but this Module shows no usages, I suspect the error comes from Hilt and ViewModel
I think when ViewModel gets initialized hilt checks the dependency graph/tree, and since it has a parameter that also needs a dependency which is the CaptureRepository , hilt also looks for it, but because your'e using #Bind, afaik, those dependencies should also define #Inject annotation.
I was able to reproduce your issue and manage to fix it by, specifying inject to your repository impl
class CaptureRepositoryImpl #Inject constructor(): CaptureRepository
another work around is having your DI module a companion object and define how hilt will provide the dependency without the need to specify #Inject in your repository impl.
#Module
#InstallIn(ViewModelComponent::class)
abstract class CaptureModule {
...
companion object {
#Provides
fun provideHomePresenter(): CaptureRepository {
return CaptureRepositoryImpl()
}
}
}
After many hours I found out a solution: I had to use #AndroidEntryPoint annotation in my Activity.
The problem is that since I'm fairly new with Compose, Hilt and Navigation I had no idea what structure I should use: I wanted to use a single Activity and instead of using Fragments for navigation I desired to use Composables.
Android Docs for Navigation provide examples about the structure I wanted; I had set up everything the same, but the only thing that was missing was that annotation. I though it was not needed since I didn't require to inject dependencies directly into the Activity, but in the end this was the root of the bug, a difficult one because the app crashed without showing a single error
Maybe I'm blind but I can't find anything about injecting a dependency that needs parameters in side a composable using dagger hilt.
Lets say my ViewModel looks something like this:
class MyViewModel #AssistedInject constructor(#Assisted myValue: Int) : ViewModel() {
...
}
and I've got a factory interface like this:
#AssistedFactory
interface MyViewModelAssistedFactory {
fun create(myValue: Int): MyViewModel
}
how can I inject that dependency with a certain value as parameter?
All answers I found where like:
#Inject
var myViewModelFactory: MyViewModelAssistedFactory;
and
val initValue = 4
fun onCreate(){
val viewModel = myViewModelFactory.create(initValue)
}
but that doesn't work inside a composable fun.
Not sure if still relevant but if you use the navigation component you can just call hiltViewModel() in the navgraph builder.
Example:
https://github.com/pablichjenkov/ComposeStudy/blob/04298ca8393d3eea0f5b7883fb223161ef79a962/app/src/main/java/com/pablichj/study/compose/home/HomeNavigation.kt#L24
If not using Jetpack Navigation then the solution is a bit more complex. You will need to create a State tree in your App where they implement LifecycleOwner and ViewModelStoreOwner, in order to be able to install the ViewModel appropriately. The good news is that there is work out there already doing so, check this:
https://github.com/Syer10/voyager
Suppose, I have ViewModel class with some UseCase in its constructor. This UseCase, on the other hand, has a CoroutineScope in its constructor. And I want to use the viewModelScope as an argument. Can I do it with Hilt?
#InstallIn(ViewModelComponent::class)
abstract class ViewModelModule {
#Provides
fun provideUseCase(scope: CoroutineScope) = MyUseCase(scope)
}
...
#HiltViewModel
class MyViewMode(useCase: MyUseCase): ViewModel() {
...
}
As far as I understand, ViewModelComponent only has SavedStateHandle as a default binding, not even ViewModel itself.
If your UseCase depends on the scope of the ViewModel that consequently depends on the UseCase, you have a dependency loop and you can't resolve it using Hilt.
You'd be better off passing down a provider for the UseCase, and initializing it inside the ViewModel, or better yet, not depend on the CoroutineScope and instead make your UseCase interface suspend
You can use KTX extensions for lifecycle-aware components, and use the viewModelScope extension. See here.
I advise against passing the scope from your viewModel as #MrMikimn mentioned. you can do the following with the new flow API in your use case instead :
suspend fun execute() = flow {
// some code
emit(SomeResult)
}
And inside your viewModel something like this :
fun doSomething() = viewModelScope.launch {
myUseCaseInstance
.execute()
.flowOn(Dispatchers.IO)
.collect { result ->
// do something with result
}
}
This way the execute function will run inside the viewModelScope so if your ViewModel gets cleared it will get canceled also. If you want more controll over this you can also instantiate a Job in viewModel and assign the doSomething() function to it something like val job: Job = doSomething() and then you can further decide when to cancel or retrigger this.
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)...
Like below?
val retro: Retro by lazy {
PilotApp.retro!!
}
class PilotApp : Application() {
companion object {
var retro: Retro? = null
}
override fun onCreate() {
retro = Retro(applicationContext)
super.onCreate()
}
}
Is this a good way of initialisation? Thanks in advance.
This is not following the rule of "Inversion of Control" and It is not a good idea.
The reason it is not a good idea is because whenever a class (like a ViewModel, Fragment or Activity or ...) wants to use the retro, they have to get the Retro object themselves by calling your first line (PilotApp.retro).
The alternative (called Dependency Injection/Inversion of Dependency) is that the Retro object is given to the class (again, the ViewModel or whatever) when it is initialized.
The reason why this is important is because with the second approach, you can make your classes that use the Retro, testable. You can give them RetroMock or TestRetro that does what you want (for example, mock an api to return an error).
Another note for your example, you don't need to make the retro nullable, you should make your var a lateinit and make it non-null.