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.
Related
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
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.
I'm developing in MVP. In my Presenter, I call my Repository thanks to the suspend function. In this suspend function, I launch a Coroutine - not on the main thread.
When this coroutine is finished, I want to execute some code: I am using withContext() in order to do so.
In my Repository, I am launching a Coroutine (and maybe I am wrong) to insert my data, using DAO, in my Room database.
When I debug my application, it goes into my Presenter but crashes before going into my Repository.
Presenter
override suspend fun insertUserResponse(activity: Activity, data: UserResponse) {
scope.launch(Dispatchers.IO) {
try {
userResponseRepository.insertUserResponse(data)
withContext(Dispatchers.Main) {
redirectToClientMainPage(activity)
}
} catch (error: Exception) {
parentJob.cancel()
Log.e("ERROR PRESENTER", "${error.message}")
}
}
}
Repository
override suspend fun insertUserResponse(userResponse: UserResponse) {
GlobalScope.launch(Dispatchers.IO) {
try {
val existingUser: UserResponse? =
userResponseDAO.searchUserByID(userResponse.profilePOJO.uniqueID)
existingUser?.let {
userResponseDAO.updateUser(userResponse)
} ?: userResponseDAO.insertUser(userResponse)
} catch (error: Exception) {
Log.e("ERROR REPOSITORY", "${error.message}")
}
}
}
I have no error shown in my logcat.
EDIT:
Scope initialization
private var parentJob: Job = Job()
override val coroutineContext: CoroutineContext
get() = uiContext + parentJob
private val scope = CoroutineScope(coroutineContext)
val uiContext: CoroutineContext = Dispatchers.Main (initialized in my class constructor)
Stack trace
I finally found the answer!
Thanks to #sergiy I read part II of this article https://medium.com/androiddevelopers/coroutines-on-android-part-ii-getting-started-3bff117176dd and it mentions that you can't catch error except Throwable and CancellationException.
So instead of catching Exception, I traded it for Throwable. And finally I had an error shown in my logcat.
I am using Koin to inject my repository and all. I was missing my androidContext() in my Koin application.
That's it.
Without stacktrace it's hard to help you.
Here is an article, that might be helpful. Replacing ViewModel mentioned in the article with Presenter in your case you can get several general recommendations in using coroutines:
As a general pattern, start coroutines in the ViewModel (Read: Presenter)
I don't see the need to launch one more coroutine in your case in Repository.
As for switching coroutine's context for Room:
Room uses its own dispatcher to run queries on a background thread. Your code should not use withContext(Dispatchers.IO) to call suspending room queries. It will complicate the code and make your queries run slower.
I couldn't find the same recommendation in official docs, but it's mentioned in one of the Google code labs.
Both Room and Retrofit make suspending functions main-safe.
It's safe to call these suspend funs from Dispatchers.Main, even though they fetch from the network and write to the database.
So you can omit launch (as well as withContext) in Repository, since Room guarantees that all methods with suspend are main-safe. That means that they can be called even from the main thread. Also you can not to define Dispatchers.IO explicitly in Presenter.
One more thing. If Presenter's method insertUserResponse is suspend, then you call it from another launched coroutine, don't you? In that case, why you launch one more coroutine inside this method? Or maybe this method shouldn't be suspend?
In my ViewModel I am calling a method from my repository class which returns a LiveData from a webservice. My viewModel code:
class MainViewModel #Inject constructor(val mainRepository: MainRepository) : ViewModel() {
val source: LiveData<My_Result> = mainRepository.fetchApiresultFromClient(str_query)
.......... }
My question is that is there a way to get real data from the webservice called in repository or do I just prepare the result and assert that its not null; something like below:
when(mainrepository.fetchApiresultFromClient(any(String::class))).thenReturn(myPreparedLiveData<My_result>)
As the question is tagged as unit-testing, IMHO, expecting real data from a web service does not actually fall into the scope of unit-testing. You might call than an integration testing, however, from the unit-testing point of view, you might consider mocking the response from the function that calls the web service and verify if the method was called using proper arguments that you expect.
Just wondering others opinion I have 2 ways I can go about doing something and was curious which is better (and hopefully why you think so)
I have 2 files WordRepository and WordViewModel. I can either do the coroutines in the Repo or in the ViewModel both ways work, but hoping someone can give me a clue as to why I would do the coroutines in one or the other and vice versa.
Version A.(Where the coroutine is in the Repo)
WordRepo:
class WordRepository(private val wordDao: WordDao): WordRepo {
#WorkerThread
override suspend fun deleteAllLogsOlderThan(XDays: Int): Int = withContext(IO) {
return#withContext wordDao.deleteAll()
}
}
WordViewModel:
class WordViewModel(private val wordRepository: WordRepo) : ViewModel() {
fun deleteAllLogsOlderThanA(XDays:Int): Int = runBlocking {
wordRepository.deleteAllLogsOlderThan(XDays)
}
}
Version B.(Where the coroutine is in the ViewModel)
Word Repo:
class WordRepository(private val wordDao: WordDao): WordRepo {
#WorkerThread
override suspend fun deleteAllLogsOlderThan(XDays: Int): Int = wordDao.deleteAll()
}
WordViewModel:
class WordViewModel(private val wordRepository: WordRepo) : ViewModel() {
fun deleteAllLogsOlderThanA(XDays:Int): Int = runBlocking {
withContext(IO) {
wordRepository.deleteAllLogsOlderThan(XDays)
}
}
}
I think either way is fine honestly but if I have to choose I prefer keeping thread related code in the repository layer. (Version A in the example)
My justification:
(A) View Models should not assume how Repositories internally work. Version B implies that the View Model assumes that the Repository will run on the calling thread.
(B) Also, the repository should not depend on other components to use a specific thread to call its method. The repository should be complete by itself.
(C) To avoid code duplication. Having multiple View models calling one repository method is a very common situation. Version A wins in this case because you only need Corountine in one place, the repository.
I suggest looking at some of the Google Samples to see where they like to handle Coroutine and data access threading codes.
Sunflower uses Coroutine code in their repository. This is probably particularly useful for you because it includes examples of fire-and-forget type of requests.
GitHubBrowserRepo does not use Coroutine but the repository keeps reference to the Executor instances that are used when accessing the data.
The question is where to specify that repository jobs should run on IO threads pools:
A) In Repository
B) In ViewModel
Frankly I am not sure which way is better.
B) Having it in ViewModel means more transparency of what runs on which thread. This is also the way I mostly worked with RxJava. As a negative your ViewModel will be cluttered with thread switching (withContext()), while it's actually obvious that all repository job should run on the background thread. So is this extra information any useful?
A) Having it in Repository means more flexibility regarding thread switching in repository and cleaner code in ViewModel. Code will be less explicit from ViewModel point of view about threads. As a negative all repository methods will need to be suspended, so thus usable only from coroutines.