Skip to content

Instantly share code, notes, and snippets.

@svenjacobs
Last active January 17, 2023 13:37
Show Gist options
  • Save svenjacobs/03ce714057e211e875f6c138e83d309c to your computer and use it in GitHub Desktop.
Save svenjacobs/03ce714057e211e875f6c138e83d309c to your computer and use it in GitHub Desktop.
An event inside a Compose State or MutableState which should only be handled once
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.State
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.listSaver
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
/**
* An event inside a [State] or [MutableState] which should only be handled once, even after
* recomposition. The [consume] function ensures this.
*
* Note that each instance of [StateEvent] is considered unique and the encapsulated [value] is
* *NOT* used for structural equality. This means that two instances of the same value are
* structurally and referentially unequal.
*
* The class exposes the [value] property. However use the [consume] function to ensure singular
* reads!
*
* @see create
* @see none
* @see of
* @see saver
*/
class StateEvent<T> private constructor(
val value: T?,
) {
private var consumed = false
private val mutex = Mutex()
enum class ConsumeResult {
Consumed, Skipped
}
/**
* Returns `true` when the event was not yet consumed and [onConsume] was called, else `false`.
*
* [onConsume] must return a [ConsumeResult] to indicate whether the event was consumed or
* skipped.
*/
suspend fun consume(onConsume: suspend (T) -> ConsumeResult): Boolean = mutex.withLock {
if (value == null || consumed) return@withLock false
consumed = onConsume(value) == ConsumeResult.Consumed
return@withLock true
}
override fun toString(): String = if (value == null) {
"StateEvent(None)"
} else {
"StateEvent(event=$value, consumed=$consumed)"
}
companion object {
fun <T : Any> create(event: T) = StateEvent(event)
fun <T> none() = StateEvent<T>(null)
fun <T : Any> of(event: T?): StateEvent<T> = if (event == null) {
none()
} else {
create(event)
}
@Suppress("UNCHECKED_CAST")
fun <T> saver(): Saver<StateEvent<T>, *> = listSaver(
save = {
listOf(
it.value,
it.consumed,
)
},
restore = {
StateEvent(it[0] as T).apply {
consumed = it[1] as Boolean
}
},
)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment