All Downloads are FREE. Search and download functionalities are using the official Maven repository.
Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
commonMain.de.halfbit.componental.state.StateHolder.kt Maven / Gradle / Ivy
package de.halfbit.componental.state
import de.halfbit.componental.ComponentContext
import de.halfbit.componental.coroutines.ComponentalDispatchers
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.withTimeout
import kotlin.reflect.KClass
import kotlin.reflect.cast
public interface StateHolder {
public val state: StateFlow
}
public abstract class MutableStateHolder : StateHolder {
public abstract fun updateState(reducer: (S) -> S)
public abstract fun updateIf(stateType: KClass, reducer: (W) -> S)
public inline fun updateIf(noinline reducer: (W) -> S) {
updateIf(W::class, reducer)
}
public abstract suspend fun updateWhen(stateType: KClass, reducer: (W) -> S)
public suspend inline fun updateWhen(noinline reducer: (W) -> S) {
updateWhen(W::class, reducer)
}
public abstract fun updateUiWhen(stateType: KClass, block: (S) -> Unit)
public inline fun updateUiWhen(noinline block: (S) -> Unit) {
updateUiWhen(S::class, block)
}
public abstract fun launchWithStateWhen(stateType: KClass, block: suspend (S) -> Unit)
public inline fun launchWithStateWhen(noinline block: suspend (S) -> Unit) {
launchWithStateWhen(S::class, block)
}
public abstract fun withStateWhen(stateType: KClass, block: (S) -> Unit)
public inline fun withStateWhen(noinline block: (S) -> Unit) {
withStateWhen(S::class, block)
}
}
public fun mutableStateHolder(
initialState: S,
coroutineScope: CoroutineScope,
): MutableStateHolder =
DefaultMutableStateHolder(
initialState,
coroutineScope,
)
public fun ComponentContext.mutableStateHolder(
initialState: S,
): MutableStateHolder =
mutableStateHolder(
initialState,
coroutineScope,
)
private class DefaultMutableStateHolder(
private val initialState: S,
private val coroutineScope: CoroutineScope,
private val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default,
private val mainDispatcher: CoroutineDispatcher = ComponentalDispatchers.optimalMain,
) : MutableStateHolder() {
private val channel = Channel<(S) -> S>(capacity = 64)
override val state: StateFlow =
flow {
emit(initialState)
var state = initialState
while (true) {
val reducer = channel.receive()
state = reducer.invoke(state)
emit(state)
}
}.onCompletion {
channel.cancel()
}.stateIn(
coroutineScope,
SharingStarted.Eagerly,
initialState,
)
override fun updateState(reducer: (S) -> S) {
@OptIn(DelicateCoroutinesApi::class)
if (channel.isClosedForSend) {
return
}
if (channel.trySend(reducer).isFailure) {
throw IllegalStateException("Failed to schedule reducer")
}
}
override fun updateIf(stateType: KClass, reducer: (W) -> S) {
updateState { state ->
if (stateType.isInstance(state)) {
reducer(stateType.cast(state))
} else state
}
}
override suspend fun updateWhen(stateType: KClass, reducer: (W) -> S) {
try {
withTimeout(5_000) {
state.first { stateType.isInstance(it) }
updateIf(stateType, reducer)
}
} catch (e: TimeoutCancellationException) {
println("State $stateType not reached")
}
}
override fun updateUiWhen(stateType: KClass, block: (S) -> Unit) {
updateState { state ->
if (stateType.isInstance(state)) {
coroutineScope.launch(mainDispatcher) {
block(stateType.cast(state))
}
}
state
}
}
override fun launchWithStateWhen(stateType: KClass, block: suspend (S) -> Unit) {
updateState { state ->
if (stateType.isInstance(state)) {
coroutineScope.launch(defaultDispatcher) {
block(stateType.cast(state))
}
}
state
}
}
override fun withStateWhen(stateType: KClass, block: (S) -> Unit) {
updateState { state ->
if (stateType.isInstance(state)) {
block(stateType.cast(state))
}
state
}
}
}