All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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.*
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.*
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) {
        val result = channel.trySend(reducer)
        if (result.isFailure && !result.isClosed) {
            throw IllegalStateException(
                "Failed to schedule reducer to a not closed channel"
            )
        }
    }

    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
        }
    }
}