How can we get the callback for the 'insert' operation in repository as Livedata and I can pass back to viewmodel using coroutines?
Dao
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(user : User): Long
I am not sure you need callback if you use coroutines in your code.
If you make your insert function as suspend, you can call it from your ViewModel from inside coroutine (for example with viewModelScope). And inside this coroutine you'll move to the next line of code only after insert method would be done:
viewModelScope.launch{
val userId = repository.insert(someUser)
// <- there coroutine is suspended until insert is done. In a sense it's like a callback
someOtherMethod()
}
Related
i am inserting and deleting a row in room database using following methods of ViewModel class
fun insert(rules: TableRules) = viewModelScope.launch {
repository.insert(rules)
}
fun delete(id: Int) = viewModelScope.launch {
repository.delete(id)
}
and retriving the data using this this method of DAO class
#Query("SELECT * FROM rules_table")
fun getAlphabetizedRules(): List<TableRules>
but i am not getting update data.
i.e when i add one row and then retrive, i will not get newly added row.
i close my app, remove from recent app list, start app again then retrive, then i will get that row.
same thing happens in case of delete also.
what i am missing i above.
Launching a coroutine queues up work that will complete in the future. If you launch a coroutine and then immediately check the state of the table without waiting for the coroutine to finish, you have a race condition and will likely get the earlier state returned.
You need to call getAlphabetizedRules() from inside the same coroutine that you launch to call insert() or delete() so it is happening after the database change.
Or alternatively, you can create a new coroutine or suspend function that joins the returned Job from your existing insert() and delete() functions. For example:
suspend fun deleteAndGetUpdatedList(id: Int): List<TableRules> {
delete(id).join()
return repository.getAlphabetizedRules()
}
By the way, in your DAO, getAlphabetizedRules() should be marked as a suspend function to make it easier to use properly (not having to use withContext(Dispatchers.IO) { } every time you call it.
Mark the DAO method with #RawQuery annotation instead of normal #Query.
I am trying to migrate from LiveData to Kotlin Flow. Right now I am working on a project that has offline supports in Room.
I was looking through the documentation and I managed to write an observable query in coroutines with Flow. (see: here)
The problem that I am facing right now is that whenever I add the suspend keyword inside the DAO class and try to run the project, it fails with the following error:
error: Not sure how to convert a Cursor to this method's return type (kotlinx.coroutines.flow.Flow<MyModel>).
The code with the problem:
#Transaction
#Query("SELECT * FROM table WHERE status = :status LIMIT 1")
suspend fun getWithSpecificStatus(status: String): Flow<MyModel?>
I am calling the code like this:
val modelLiveData: LiveData<MyModel?> = liveData(Dispatchers.IO) {
val result = databaseService.getWithSpecificStatus(Enum.IN_PROGRESS.status).first()
result?.let {
emit(it)
}
}
I tried to keep things simple. why is my code failing?
You could directly initialise the value of modelLiveData as:
val modelLiveData=databaseService.
getWithSpecificStatus(Enum.IN_PROGRESS.status).first()
.asLiveData()
You have used Flow so asLiveData() is used to convert it to LiveData
Also suggestion , you do should not use the suspend keyword because when you are returning Flow, Room automatically does this asynchronously. You just need to consume the Flow.
I get this error: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.
It happens when I launch fun turnAllWordsOn() in the ViewModel (code below). This function launches coroutine, and I thought that coroutine always works on the backgroung thread. So why I get this error?
Apprecieate any help
In Fragment:
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.action_turn_all_words_on -> {
viewModel.turnAllWordsOn()
true
}
// othes items
}
In ViewModel:
fun turnAllWordsOn() = viewModelScope.launch {
wordDao.turnAllWordsOn()
}
In Dao:
#Query("UPDATE word_table SET shown = 1")
fun turnAllWordsOn()
You have to mark your Dao function as a suspend function if you want Room to run it on a background thread. Otherwise all you're doing is calling a synchronous function from a coroutine scope.
#Query("UPDATE word_table SET shown = 1")
suspend fun turnAllWordsOn()
As a side note, suspend functions don't automatically run on a background thread, however Room does the necessary work behind the scenes when you mark a query as suspend.
Even if you have the answer, I still want to give some solution and cause of the problem,
Performing networking, or accessing the database can block Ui Thread, so you can use
Using RxJava:
Completable.fromAction {
wordDao.turnAllWordsOn()
}
Using Coroutine:
#Query("UPDATE word_table SET shown = 1")
suspend fun turnAllWordsOn()
Just a note for anyone who ends up here the accepted answer is correct, however if you are calling a Dao function from an activity you must put in inside a Coroutine scope. So after you add suspend onto your Dao function call it from the activity like this.
lifecycleScope.launch(Dispatchers.IO){
// call Dao function here
}
I'm trying to rewrite my Kotlin database interface to use it with coroutines, so I made all the functions suspend. But when I run the application I get the error: Not sure how to convert a Cursor to this method's return type.
Failing code:
#Dao
interface PostDao {
#Query("SELECT * FROM post")
suspend fun getAll(): LiveData<List<Post>>
}
The function which failed the build returns the LiveData object and this seems to be the problem, because if I remove the "suspend" word or LiveData, the app works properly.
Working variant:
#Dao
interface PostDao {
#Query("SELECT * FROM post")
fun getAll(): LiveData<List<Post>>
}
Could anyone explain why does it work so? Is there a way to use suspend with function returning LiveData?
It doesn't really make sense to use a suspend function to return a LiveData. Generating a LiveData instance is a non-blocking action, so there's no reason for it to suspend. In the case of LiveData, the blocking data request happens in the background and updates the already-returned LiveData when it's ready, rather than waiting for the data and then generating the LiveData.
If you use a suspend function for your data, you would just return List<Post>. Calling this suspend function would make the request a single time, suspend until the data is ready, and then return it in your coroutine.
If you want to continually receive updated data, what you need is a coroutine Flow. Since a Flow is cold, you do not use a suspend function for it:
#Dao
interface PostDao {
#Query("SELECT * FROM post")
fun getAll(): Flow<List<Post>>
}
Then in your view model layer, you can either convert it to a LiveData:
val postsLiveData = repository.getAll().asLiveData()
or you can convert it to a hot StateFlow or SharedFlow, where consensus seems to be that it should be preferred over LiveData since it is not tied directly to Android details:
val postsSharedFlow = repository.getAll().shareIn(viewModelScope, SharingStarted.Eagerly, 1)
You can read about subscribing to SharedFlow and StateFlow in the documentation.
I am following kotlin android fundamental codelabs. There is a part where the app need a reference of all data from database
The following code is placed on ViewModel, database is passed as the instance of dao
val nights = database.getAllNights()
The implementation of getAllNights is as follows (In DAO)
#Query("SELECT * FROM daily_sleep_quality_table ORDER BY nightId DESC")
fun getAllNights(): LiveData<List<SleepNight>>
All other database calls are delegated to private suspend function and dispatched to IO thread except that one.
Isn't this call block the main thread
Why is it allowed
CODELAB