I am trying to get data from Room Database using async & await
inside Coroutine Scope
but getting problem while returning value.
Here is my code:
fun getUserFromDB():Profile {
val profileDao = AppDatabase.getDatabase(context).getProfileDao()
CoroutineScope(Dispatchers.IO).launch {
return profileDao.getUserProfile().await()
}
}
Dao:
@Query("SELECT * FROM PROFILE LIMIT 1")
suspend fun getUserProfile():Deferred<Profile>
Here I want to return userProfile from the method but I cannot do that inside scope and it will be null if I return from outside of Coroutine scope
.
Note: I am not following MVVM pattern here but doing a simple example.
There are a couple things wrong here but I'll first address the main issue.
fun getUserFromDB() {
val profileDao = AppDatabase.getDatabase(context).getProfileDao()
CoroutineScope(Dispatchers.IO).launch {
val userProfile = profileDao.getUserProfile().await()
}
return userProfile
}
In getUserFromDB
, the launch
executes asynchronously from getUserFromDB
and will not complete before you return.
getUserFromDB
as a suspend function. (Highly recommended)suspend fun getUserFromDB() {
val profileDao = AppDatabase.getDatabase(context).getProfileDao()
val userProfile = withContext(Dispatchers.IO) {
profileDao.getUserProfile().await()
}
return userProfile
}
runBlocking
.fun getUserFromDB() {
val profileDao = AppDatabase.getDatabase(context).getProfileDao()
val userProfile = runBlocking(Dispatchers.IO) {
profileDao.getUserProfile().await()
}
return userProfile
}
getUserProfile
should not return Deferred<Profile>
(especially if it's already suspending), as this is an unsafe primitive to be passing around, it should just return Profile
. This way you don't introduce unwanted concurrency by accident and you don't have to write await()
.@Query("SELECT * FROM PROFILE LIMIT 1")
suspend fun getUserProfile(): Profile
fun getUserFromDB() {
val profileDao = AppDatabase.getDatabase(context).getProfileDao()
CoroutineScope(Dispatchers.IO).launch {
val userProfile = profileDao.getUserProfile() /* .await() */
}
return userProfile
}
Dispatchers.IO
), it is safe to call in the main thread/dispatcher. More info in this answer Why does the querySkuDetails need to run in IO context? .fun getUserFromDB() {
val profileDao = AppDatabase.getDatabase(context).getProfileDao()
CoroutineScope(/* Dispatchers.IO */).launch {
val userProfile = profileDao.getUserProfile().await()
}
return userProfile
}
CoroutineScope
without cancelling it later, you should just use GlobalScope
to reduce the number of allocations. People say "Avoid using GlobalScope
.", which is true but you should equally avoid creating CoroutineScope
s without cancelling them (I'd argue it's even worse).fun getUserFromDB() {
val profileDao = AppDatabase.getDatabase(context).getProfileDao()
GlobalScope.launch(Dispatchers.IO) {
val userProfile = profileDao.getUserProfile().await()
}
return userProfile
}
I've structured my answer so that you can apply each change independently when you are ready. If you just want the sum of all the changes then here it is.
@Query("SELECT * FROM PROFILE LIMIT 1")
suspend fun getUserProfile(): Profile
suspend fun getUserFromDB(): Profile {
val profileDao = AppDatabase.getDatabase(context).getProfileDao()
val userProfile = profileDao.getUserProfile()
return userProfile
}
Happy coding. :)