Last active
January 17, 2023 13:37
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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