Good day, I'm working through the coroutines with room codelab and I have some doubt as to when it's okay to simply call a databaseDao from a ViewModel and when you need to put it inside a suspend fun and launch it from a Coroutine with Dispatchers(IO).
In particular, in the code, which can be found here, inside SleepTrackerViewModel on line 37 they declare a val directly in the class like this:
private val nights = database.getAllNights()
Where the database is the dao and getAllNights is a query and it's called without any Coroutine or suspend fun. Shouldn't this be done from a coroutineScope within a suspend fun?
Thanks in advance!
As seen in the SleepDatabaseDao, getAllNights() is defined as:
fun getAllNights(): LiveData<List<SleepNight>>
Getting a LiveData does not need to be called on a background thread or from within a coroutine scope.
Related
Recently I saw this - Most data sources already provide main-safe APIs like the suspend method calls provided by Room or Retrofit. Your repository can take advantage of these APIs when they are available.
What does this mean? Is the dispatcher under the hood Dispatcher.IO for Retrofit and Room? Or do I need to mention that explicitly, while making the request? Thank you.
withContext(Dispatchers.IO) {
// Some retrofit call or room query
}
No you don't need to mention dispatchers for Retrofit and Room. For Room when you mark a dao function as suspend fun it is guaranteed that it will not block main thread.
You can read this article https://medium.com/androiddevelopers/room-coroutines-422b786dc4c5
from the article
Room calls the CoroutinesRoom.execute suspend function, which switches to a background dispatcher, depending on whether the database is opened and we are in a transaction or not.
No, you don't need to switch context when calling suspend functions of Retrofit and Room. I'm not sure if they use Dispatcher.IO under the hood, maybe they use their custom context composed of thread pools, but it is guaranteed to be called in background thread.
For example you can call suspend Dao functions in ViewModel class like the following:
viewModelScope.launch {
val user dao.getCurrentUser()
// Update UI using user
}
assuming getCurrentUser() is a suspend function:
suspend fun getCurrentUser(): User
Marking your Retrofit HTTP request methods and Room DAO query methods as suspend tells both respective libraries to do the asynchronous work for you, meaning that you don't have to explicitly change threads with Dispatchers.IO at all.
Furthermore, even when a Room DAO method isn't marked suspend, but it returns a value wrapped in an Observable such as Kotlin's Flow, or RxJava's Flowable, or Jetpack's LiveData, Room will then run those queries asynchronously for you as well. As per the documentation.
That being said, you should still launch coroutines in that case, whenever you call your async, non-blocking methods with lifecycleScope or viewModelScope depending on where you're calling them from (Activity/Fragment or ViewModel) to harness the full power of suspending functions. lifecycleScope and viewModelScope use Dispatchers.Main.immediate by default, as already stated, you will not need to change Dispatchers.
There are apparently Kotlin coroutines extension functions for SqlDelight, but I don't know how to implement them since I can't find documentation.
I have a normal query that looks like this:
val allItems
get() = itemQueries.selectAll().mapToList()
Can I turn this into a suspend function?
There is currently (v1.2.1) no suspend function support for SqlDelight queries, however you can consume a Coroutines Flow object, which is even better. To do this you need to add the coroutines extension library in your app gradle:
dependencies {
implementation "com.squareup.sqldelight:coroutines-extensions:1.2.1"
}
Then turn your query into this:
val allItems: Flow<List<Item>> =
itemQueries.selectAll()
.asFlow()
.mapToList()
This flow emits the query result, and emits a new result every time the database changes for that query.
You can then .collect{} the results inside a coroutine scope.
For single-shot queries, you don't need the coroutine extension library. Instead, just do:
suspend fun getAllItems() = withContext(Dispatchers.IO) {
itemQueries.selectAll().mapToList()
}
The other answer is specific to when you want to react to changes in the database.
I want to bind a button click to a suspended function inside viewmodel.
this is my code:
RegistrationActivityViewModel.kt
suspend fun register() {
if (validateFields()) {
val result = SourceplusplusApiService.invoke().registerUser(username.value!!, email.value!!, password.value!!).await()
isRegistrationCompleted.value = getResultValue(result)
}
}
activity_registration.xml
<Button
android:text="Register"
android:onClick="#{()->viewmodel.register()}"
android:textSize="16sp" />
i get a databinding error that says ActivityRegistrationBindingImpl not generated. after searching a lot i realized that when i remove the suspend keyword and comment the code inside, it works fine but it has to be a suspended function.
Does anyone know how to fix it?
You cannot data bind to a suspend function, and IMHO a viewmodel should not be exposing a suspend function in the first place. I recommend:
Step #1: Remove the suspend keyword from register()
Step #2: Rewrite register() to run your code in a suitable coroutine scope, so any suspend functions that it calls are handled properly:
fun register() {
viewModelScope.launch(Dispatchers.Main) {
if (validateFields()) {
val result = SourceplusplusApiService.invoke().registerUser(username.value!!, email.value!!, password.value!!).await()
isRegistrationCompleted.value = getResultValue(result)
}
}
}
Here, I am using the viewModelScope option provided by androidx.lifecycle:lifecycle-viewmodel-ktx version 2.1.0-alpha01 and newer. Alternatively, manage your own coroutine scope. Dispatchers.Main will ensure that any results of that work is available to you on the Android main application thread.
Now, your data binding expression can refer to register(), while you still have a coroutine scope for calling downstream suspend functions.
In the codeLabs tutorial (Android - Kotlin - Room with a View), they have used "viewModelScope.launch(Dispatchers.IO)" to call insert method. what exactly it is and why is it used for.
Refer the link,
https://codelabs.developers.google.com/codelabs/android-room-with-a-view-kotlin/#8
fun insert(word: Word) = viewModelScope.launch(Dispatchers.IO) {
repository.insert(word)
}
viewModelScope is a CoroutineScope which is tied to your ViewModel. it means that when ViewModel has cleared coroutines inside that scope are canceled too.
Dispatchers.IO means that suspend fun repository.insert(word) will run in IO thread which is managed by kotlin.
there are different Dispachres. Dispatchers.IO is used for IO works like database or remote server. Dispatchers.Default is used for tasks that has high CPU usage. Dispatchers.Main is used for tasks that need to update UI.
My understanding is that Kotlin's coroutines are libraries, which leaves the only language-level feature of concurrency in Kotlin as the suspend keyword.
I'm still wrapping my head around coroutines in Kotlin, but I'm wondering if that may be overkill for my problem, which is updating a text view as soon as an HttpsURLConnection returns data. exception handling makes callbacks ugly enough that I want to avoid those if possible
does the suspend keyword simply mean that the runtime may suspend a function that takes a while to complete? or is suspension only enabled inside a coroutine? as a hypothetical, can I write
suspend fun getStringFromNetwork(): String {
val request = URL("https:stackoverflow.com").openConnection()
val result = readStream(request.inputStream)
request.disconnect()
return result
}
//and then elsewhere
foo()
val s = getStringFromNetwork()
bar(s)
baz()
and know that if getStringFromNetwork downloads 1 GB of data that baz() will be called in the meantime, while bar(s) waits for s to be populated by getStringFromNetwork?
The "and then elsewhere" part calls getStringFromNetwork(), so it won't compile outside a suspend function (including suspend lambdas), and they can only be executed inside coroutines.
that baz() will be called in the meantime, while bar(s) waits for s to be populated by getStringFromNetwork?
No, if you write it this way, baz() will only start executing after bar(s) returns. But of course bar(s) can start a new coroutine which will do the actual work.