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
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 understand room db with LiveData. I was following one of Googles videos on Room. In the project they have specified:
In Dao:
#Query("SELECT * FROM daily_sleep_quality_table ORDER BY nightId DESC")
fun getAllNights(): LiveData<List<SleepNight>>
In ViewModel:
private val nights = database.getAllNights()
In the viewModel, they are directly calling getAllNights(). And for other Dao methods which doesn't have LiveData they are calling from a suspend function. How does this work?
liveData is a good structure that when its values changed, all of its callbacks in other places reflect the changes so you can use this in place of suspend func in coroutines
but it is not recommended i suggest you always use coroutines suspend function in Dao class as you there is alo f benefit in coroutine for long runtime functions such as reading from database.
Use LiveData with Room
The Room persistence library supports observable queries, which return LiveData objects. Observable queries are written as part of a Database Access Object (DAO).
Room generates all the necessary code to update the LiveData object when a database is updated. The generated code runs the query asynchronously on a background thread when needed. This pattern is useful for keeping the data displayed in a UI in sync with the data stored in a database. You can read more about Room and DAOs in the Room persistent library guide.
For more details read:
https://developer.android.com/topic/libraries/architecture/livedata#use_livedata_with_room
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.
For now I use this code in my DAO:
#Dao
interface NotesDao {
#Query("SELECT * FROM Notes")
fun getAllNotes(): List<Notes?>?
#Query("SELECT * FROM Notes WHERE not hidden AND not grouped")
fun getNotes(): List<Notes?>?
#Query("SELECT * FROM Notes WHERE id = :id")
fun getById(id: Long): Notes?
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(notes: Notes?)
#Update
fun update(notes: Notes?)
#Delete
fun delete(notes: Notes?)
}
But in many tutorials I either see Flowable<List<Notes>> instead of List<Notes> or LiveData<List<Notes>>.
Which approach is preferable?
There are two factors you should consider:
All interactions with database (both queries and updates) should be outside main thread.
In some use-cases you want single "one-shot" query, in other - you want your query to be "live" - i.e. to return updated result whenever data was changed (Observer pattern).
Using Kotlin you have two main options (what's better is a matter of taste. In some tutorials you can see also AsyncTask or Thread executors for switching off the main thread):
Use RxJava (use Flowable for "live" queries, Single - for "one-shot" queries and one of the Single/Completable/Maybe for insert/update/delete). Using RxJava you can use built-in mechanism for switching off the main thread.
Use Coroutines (and keyword suspend) for insert/update/delete, and LiveData or Flow (from coroutines library) for "live" queries, or suspend - for "one-shot" queries. suspend lets to switch from the main thread. If you use LiveData/Flow you don't need suspend, since Room does it itself.
See also official documentation for additional hints.
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()
}