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.
Related
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'm working on implementing Room in an android app and I have a use case where I am receiving a Flow with the current user id for functionality similar to switching which logged in Google user to view a service as. I'd like to pass that flow into a #Query annotated method in my DAO to get user widgets so that if the currently selected user changes or the list of widgets stored changes, the output Flow<List> would change as well.
Something like
WidgetRepo.kt
val widgets: Flow<List<Widget>> = widgetDAO.getWidgetsByUser(currentUserID)
WidgetDao.kt
#Query("select * from widget where userID = :userID")
fun getWidgetsByUser(userID: Flow<Int>): Flow<List<Widget>>
I don't think that's a good practice. I would rather do the following logic in a specific use-case, for example:
class GetWidgetsByUserUseCase(
private val userRepo: UserRepository,
private val widgetLocalSource: WidgetRepository
){
suspend operator fun invoke() = userRepo.flatMapLatest { user ->
widgetLocalSource.getWidgetsByUser(user.id)
}
}
With this implementation every time a new user is emitted, the widgetLocalSource.getWidgetsByUser() will be triggered and the previous flows will be canceled.
See flatMapLatest for more information on the operator.
Room Database has supported Flow since before. Add this to gradle:
// Kotlin Extensions and Coroutines support for Room - latest Room version 2.3.0
implementation("androidx.room:room-ktx:$room_version")
See more Write observable queries with Room here.
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.
https://developer.android.com/topic/libraries/architecture/coroutines
Android coroutines plus liveData documentation states that we can use the liveData builder function in case we want to want to perform async operations inside the live data function
val user: LiveData<User> = liveData {
val data = database.loadUser() // loadUser is a suspend function.
emit(data)
}
val user: LiveData<Result> = liveData {
emit(Result.loading())
try {
emit(Result.success(fetchUser())
} catch(ioException: Exception) {
emit(Result.error(ioException))
}
}
I tried installing the lifecycle-viewmodel-ktx library but couldn't find this block.
Where is it located?
Try:
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0-alpha01'
The function lives here:
https://android.googlesource.com/platform/frameworks/support/+/refs/heads/androidx-master-dev/lifecycle/livedata/ktx/src/main/java/androidx/lifecycle/CoroutineLiveData.kt
And is (currently) defined as:
#UseExperimental(ExperimentalTypeInference::class)
fun <T> liveData(
context: CoroutineContext = EmptyCoroutineContext,
timeoutInMs: Long = DEFAULT_TIMEOUT,
#BuilderInference block: suspend LiveDataScope<T>.() -> Unit
): LiveData<T> = CoroutineLiveData(context, timeoutInMs, block)
I also had this problem, I would recommend just adding the dependencies they suggested here.
The issue is Google's Android docs on coroutines were not explicit in mentioning that these ktx extensions specifically (as you would see in the link) are very important to be able to get the liveData builder that supplies the LiveDataScope.
Don't make the mistake of thinking that you can just use lower versions i.e 2.1.0, just use it as explicitly specified in the doc i.e. the alpha/RC versions as in 2.2.0-alpha01.
I am new to Kotlin, coming from C# it I am quite used to async\await
How do I wait for tvClient to get the response before returning the list of channels?
override fun getChannels(): MutableList<Channel> {
disposable = tvClient.getChannels()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{Log.d("***", it.toString())},
{Log.d("***",it.toString())}
)
TODO("wait for tvClient to return results")
return mChannels;
}
I tried using coroutines but no luck
What is the best way to wait for async operation to complete in Kotlin?
You're using RxJava and thus you should implement it in a reactive way.
If you're app is not build for it yet, you can get the value blocking. Assuming getChannels() returns a single you could just call blockingGet() instead of subscribe().
But be aware that this blocks the thread the outer getChannels() is called from.
Using coroutines might be better for you. It's a little nearer to what you know from C# and with the retrofit2-kotlin-coroutines-adapter you can integrate directly with Retrofit.
You could look into using the retrofit coroutine adapters from Jake Wharton https://github.com/JakeWharton/retrofit2-kotlin-coroutines-adapter
you can check a functional implementation of kotlin v1.3 retrofit + stable coroutines using DSL here https://github.com/eriknyk/networkcall-sample/commits/master
DSL template:
fun <RESPONSE: DataResponse<*>, DATA: Any> networkCall(block: CallHandler<RESPONSE, DATA>.() -> Unit): MutableLiveData<Resource<DATA>>
= CallHandler<RESPONSE, DATA>().apply(block).makeCall()
interface DataResponse<T> {
fun retrieveData(): T
}
and using it:
fun getRepos(query: String) = networkCall<ResposResponse, List<Repo>> {
client = githubService.getRepos(query)
}
Hope it helps.