Issue
Initially I directly accessed a viewModel funtion which launched a viewModel scope coroutine for the query I was running. That had the same error.
Then, I changed the viewModel function to a suspend function and called it from a coroutine in the fragment. that didn't work either.
So, I made it such that the function was called inside a coroutine which then ran another coroutine in the viewModel scope. That gave the same outcome.
I thought it might be too much load to call it during fragment creation. so I tried calling the viewModel function using a button onclick listener. again crashed.
I ran the same query in the database inspector where it worked fine. so, the query isn't the issue either.
In the below screenshot I have included every necessary detail regarding the issue. Just focus on the highlighted content. start from pass List Fragment(top-left tab). From there, a call to the viewModel function in the top right tab. from there the DAO right below it. then the data class below it.
the viewModel function -
fun resetAllAccess(){
viewModelScope.launch {
passwordDao.resetAccessForAll()
}
}
The DAO funtion -
@Query("UPDATE password_data SET access = 0 WHERE access = 1")
fun resetAccessForAll()
Data class for the database -
@Entity(tableName = "password_data")
data class PasswordData(
@PrimaryKey(autoGenerate = true) val id: Int = 0,
@ColumnInfo(name = "service_name") var serviceName: String,
@ColumnInfo(name = "service_password") var servicePassword: String,
@ColumnInfo(name = "is_an_application") var isAnApplication: Boolean = false,
@ColumnInfo(name = "use_finger_print") var useFingerPrint: Boolean = true,
@ColumnInfo(name = "access") var access: Boolean = false
)
the call being made from the fragment -
CoroutineScope(Dispatchers.IO).launch { viewModel.resetAllAccess() }
Solution
It's because you are using the viewModelScope. Lots of people don't know this, but viewModelScope is actually hardcoded to use the main thread instead of another one.
You can find this info in Google official documentation:
Note: The viewModelScope property of ViewModel classes is hardcoded to Dispatchers.Main. Replace it in tests by using Dispatchers.setMain with a TestCoroutineDispatcher as explained in the Easy coroutines in Android: viewModelScope blog post.
So you probably want to either pass the the coroutine scope into the view model class constructor (preferred way) or create one directly in the view model. Then you should just use this to launch the coroutine.
Another practice that is commonly used is to create a scope with both view model and a custom scope (passed through the view model constructor).
For example:
class ViewModelClass(customCoroutineScope: CoroutineScope): ViewModel {
private val backgroundScope: CoroutineContext = customCoroutineScope.coroutineContext + viewModelScope.coroutineContext
fun resetAllAccess(){
backgroundScope.launch {
passwordDao.resetAccessForAll()
}
}
}
You can also find more info here about how viewModelScope works under the hood.
There's also another option, but I wouldn't recommend this at all, for obvious reasons, that is to allow Room to run queries in the main thread by using the allowMainThreadQueries in the Room builder.
Answered By - Daniel Beleza

0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.