question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Provide atomic way to transform MutableStateFlow's state

See original GitHub issue

Some state updates require to know the current state. A simple example is a StateFlow<List<T>> which we want to maintain by subscribing to created/deleted/modified events about these T objects. For each event, we need to read the existing List in the state, and set the state to the new modified list.

My current use case is the following (uses a map, but the idea is the same):

suspend fun ChromeBrowserSession.watchTargetsIn(coroutineScope: CoroutineScope): StateFlow<Map<TargetID, TargetInfo>> {
    val targetsFlow = MutableStateFlow(emptyMap<TargetID, TargetInfo>())

    target.events().onEach { targetsFlow.value = targetsFlow.value.updatedBy(it) }.launchIn(coroutineScope)

    // triggers target info events
    target.setDiscoverTargets(SetDiscoverTargetsRequest(discover = true))
    return targetsFlow
}

private fun Map<TargetID, TargetInfo>.updatedBy(event: TargetEvent): Map<TargetID, TargetInfo> = when (event) {
    is TargetEvent.TargetCreatedEvent -> this + (event.targetInfo.targetId to event.targetInfo)
    is TargetEvent.TargetInfoChangedEvent -> this + (event.targetInfo.targetId to event.targetInfo)
    is TargetEvent.TargetDestroyedEvent -> this - event.targetId
    is TargetEvent.TargetCrashedEvent -> this - event.targetId
}

This is all fine, but sometimes we may want the MutableStateFlow to be accessed concurrently from multiple places. I used to have separate subscription methods for different kinds of events, and had to use 4 coroutines to subscribe to each kind until I created a different API for all events(). In that case, I had to externally synchronize operations to ensure that I would read and write the state atomically, value = value.updatedBy(event) doesn’t really work on its own.

There is already compareAndSet which provides this kind of guarantees, but I wish there were a transform method like:

/** Atomically sets the value of this `MutableStateFlow` by applying the given [transform] on the current state. */
fun <T> MutableStateFlow<T>.transformValue(transform: (currentState: T) -> T)

Is there already something like this? Am I missing something here?

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Reactions:1
  • Comments:14 (14 by maintainers)

github_iconTop GitHub Comments

2reactions
joffrey-bioncommented, Nov 26, 2020

Very clear explanation, thanks a lot. Yes, this implementation has problems, that’s why I was thinking that having something smarter in the library would prevent wrong user-implementations like this. But I guess there is no simple solution to this, so I guess I understand why you prefer not to provide anything rather than providing some risky primitive.

Again thanks for your time explaining this. Please feel free to close this issue.

1reaction
qwwdfsadcommented, Nov 26, 2020

Some context: StateFlow and SharedFlow are using synchornized(this) as an implementation detail. Synchronization there is fast and fine-grained and no coroutines/collectors are ever resumed under the lock. This is just more faster/simpler alternative to lock-free implementation (taking into account all the constraints).

Now the problem with transformAndSet is that now we expose the primitive that invokes a user-supplied arbitrary transform that is executed under the lock. And this lambda can block indefinitely, be really slow, execute disk IO etc. All of that while holding the lock (-> preventing the progress of all emitters and collectors). While it may work for you, because you may be aware of these limitations, such API exposed as a general-purpose one is just a time-bomb waiting to explode. So I’d suggest you to stick with your extension method, but it’s unlikely that we want incorporate it in the default API

Read more comments on GitHub >

github_iconTop Results From Across the Web

Atomic Updates on MutableStateFlow | Geek Culture - Medium
An activity or fragment may consume said flow and use the values emitted by it to change the UI state. Note that I'm...
Read more >
Android "Transformations.map()" for MutableStateFlow
I've created the following extension for such case: inline fun <T, R> Flow<Iterable<T>>.mapLatestIterable(crossinline transform: (T) -> R): ...
Read more >
Make sure to update your StateFlow safely in Kotlin!
Hi, today I come to you with a quick tip on how to update your StateFlows safely in Kotlin. Recently a new version...
Read more >
MutableStateFlow - Kotlin
Atomically compares the current value with expect and sets it to update if it is equal to expect. The result is true if...
Read more >
Make sure to update your StateFlow safely in Kotlin! - Droidcon
Expose atomic updates on MutableStateFlow · Issue #2720 · Kotlin/kotlinx. ... How to collect this state now in Fragment for example?
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found