I am trying to wrap my head around flow/coroutines...
# compose-desktop
y
I am trying to wrap my head around flow/coroutines all at the same time as learning compose-desktop so clearly I am struggling 😉. I have the following piece of code:
Copy code
private val _pendingIOs = MutableStateFlow(0)
    val pendingIOs: StateFlow<Int>
        get() = _pendingIOs
    
    fun createPart(): Flow<Part> = flow {
        val e = coroutineScope {
            withContext(Dispatchers.Main) {
                <http://logger.info|logger.info>("executing in Main")
                _pendingIOs.value++
                try {
                    async(<http://Dispatchers.IO|Dispatchers.IO>) {
                        delay(100) // simulate io delay
                        Part(_nextPartId++, "xxx")
                    }.await()
                } finally {
                    <http://logger.info|logger.info>("executing in Main")
                    _pendingIOs.value--
                }
            }
        }
        emit(e)
    }
The idea being that I am updating a pendingIO state variable prior to executing any IO. The UI can then simply display a CircularProgressIndicator when said stateflow variable is > 0. Is this an acceptable way of doing things? Is there a better way? Is my flow code correct or could be simplified?
j
cc @Adam Powell This seems to have a lot of Adam's keywords and thought space
a
The placeholder code is a little convoluted but yes, the basic idea of refcounting outstanding work and displaying a progress indicator based on that state will work
I might recommend declaring
pendingIOs
as
Copy code
var pendingIOs by mutableStateOf(0)
  private set
instead if it's primarily compose code that will consume this object/data, since you'll be able to skip some other flow subscription management and the code gets simpler
y
@Adam Powell thank you Adam! if you have more recommendation for alleviating the "convoluted" code, I am all ears 🙂
a
coroutineScope {}
wrapping
withContext
is redundant; `withContext`'s behavior is a superset of
coroutineScope
already.
async(...) { ... }.await()
is a more complicated way of saying
withContext(...) {}
and since the flow returned by
createPart
only ever emits a single value and then terminates, it would be better expressed as
suspend fun createPart(): Part
unless
logger
is not thread-safe, nothing else done by
createPart
's pending count tracking (regardless of whether it's a MutableStateFlow or mutableStateOf) needs to be on the main thread, so you can drop the
Dispatchers.Main
switch
though if you have other IO operations declared on the context you'll need something else to make the read+write of the
++
and
--
atomic
y
Thank you very much for the feedback. And yes I am worried of
++
and
--
which are obviously not thread safe which is why I was running in on the Main context
a
a simple lock or compareAndSet loop would do the trick with a less drastic instrument