Hello! I'm using SQLDelight for my project and I'm...
# squarelibraries
r
Hello! I'm using SQLDelight for my project and I'm trying to understand what's the "correct" way of doing a query returning a Flow. My goal is to make sure the DB access happens on
IO
and the transformation of data happens on
Default
. I have something like this:
Copy code
fun someQuery(): Flow<List<SomeItem>> = database.transactionWithResult {
  database.someQueries.someItems(someMapper)
    .asFlow()
    .flowOn(IO)
    .mapToList()
    .distinctUntilChanged()
    .map { someTransformation(it) }
    .flowOn(Default)
}
Is the
flowOn(IO)
required?. What about this:
Copy code
...
  database.someQueries.someItems(someMapper)
    .asFlow()
    .mapToList(IO)
    .distinctUntilChanged()
    .map { someTransformation(it) }
    .flowOn(Default)
...
Would that make any difference? From my understanding,
mapToList()
will use
Default
if we don't pass a context but I want to make sure
IO
is used during the DB access and
Default
during the transforming the data. Thanks in advance!
b
In Kotlin/JVM, the
Default
and
IO
thread are from basically the same thread pool, so a
Default
dispatcher thread can be reused for
IO
dispatcher. I don't know SQLDelight all too well, but typically you wouldn't need to use the
flowOn()
on a flow from any library, as it should handle using the right dispatcher before you have access to the stream. The
mapToList()
seems to already handle this internally, and you are calling to ensure that
someTransformation(it)
is also in the
Default
dispatcher. Honestly, I don't believe you need the
flowOn(IO)
or any reference to the
IO
dispatcher at all, especially if you are ending your flow construction with
flowOn(Default)
. By using that, you're essentially stating that everything above the
flowOn()
will run in the dispatcher provided (except any part that is handled by an explicit dispatcher). When you collect, that collect will take on the dispatcher of the scope you provide.
TL;DR -- You are probably fine with just having
flowOn(Default)
at the end of your flow setup.
r
thanks
but what if this is running on Android?
wouldn't be more "correct" to use
IO
in the
mapToList(IO)?
b
If you are using a scope backed by
Dispatcher.Main
, your collect function will be in the main thread.
r
I'm building a multiplatform library for Android/iOS/JVM basically
so maybe in JVM using Default or IO won't make a lot of difference but I believe it does on Android
b
The
flowOn()
only affects upstream* *Some exceptions
r
yes but I want to make sure
mapToList()
gets executed on
IO
when using Android
I'm saying
mapToList
because it seems here is where the access to the DB happens
b
Honestly, it shouldn't matter, since Default is also a background thread on Android. The difference between Default and IO is that the former only uses a set number of threads, while the latter uses an unbounded number of threads
r
got it! thanks for the info
b
Yea, sorry I couldn't more help with SQLDelight. I am curious if the
asFlow()
extension function can be collected safely from a main dispatcher. From what I saw, it's emitting results straight from a listener to the query, but not sure what thread that listener is emitting from.
r
maybe @jw could give us some insight?
b
Answered my own question. The listener emits in whatever thread the insert or data change occurs in. Even then, it doesn't matter what thread the listener is in, the
trySend()
in the listener just dumps the data into a channel, while the flow waits on the channel to provide data. It then emits on the dispatcher operating on the flow (whether that is from
flowOn()
or the dispatcher in the coroutine scope). Long story short: It doesn't matter what thread the listener emits in.
Honestly, I feel silly not realizing that right away, since that is precisely how a
callbackFlow
works...
Well... not precisely. It's basically how it works.