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

me.aartikov.sesame.loading.simple.Loading.kt Maven / Gradle / Ivy

package me.aartikov.sesame.loading.simple

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.*

/**
 * Helps to load data and manage loading state.
 */
interface Loading {

    /**
     * Loading state.
     */
    sealed class State {
        /**
         * Empty data has been loaded or loading is not started yet. Data is empty if it is null or [isEmpty] returns true for it.
         */
        object Empty : State()

        /**
         * Loading is in progress and there is no previously loaded data.
         */
        object Loading : State()

        /**
         * Loading error has occurred and there is no previously loaded data.
         */
        data class Error(val throwable: Throwable) : State()

        /**
         * Non-empty data has been loaded. [refreshing] is true when second loading is in progress.
         */
        data class Data(val data: T, val refreshing: Boolean = false) : State()
    }

    /**
     * Loading event.
     */
    sealed class Event {
        /**
         * An error occurred. [stateDuringLoading] a state that was during the failed loading.
         */
        data class Error(val throwable: Throwable, val stateDuringLoading: State) : Event() {

            /**
             * Is true when there is previously loaded data. It is useful to not show an error dialog when a fullscreen error is already shown.
             */
            val hasData get() = stateDuringLoading is State.Data
        }
    }

    /**
     * Flow of loading states.
     */
    val stateFlow: StateFlow>

    /**
     * Flow of loading events.
     */
    val eventFlow: Flow>

    /**
     * Requests to load data.
     * @param fresh indicates that fresh data is required. See [OrdinaryLoader.load] and [FlowLoader.load]
     * @param reset if true than previously loaded data will be instantly dropped and in progress loading will be canceled.
     * If false than previously loaded data will be preserved until successful outcome and a loading request will be ignored if another one is already in progress.
     */
    fun load(fresh: Boolean, reset: Boolean = false)

    /**
     * Requests to cancel in progress loading.
     * @param reset if true than state will be reset to [Loading.State.Empty].
     */
    fun cancel(reset: Boolean = false)

    /**
     * Mutates [Loading.State.Data.data] with a [transform] function.
     */
    fun mutateData(transform: (T) -> T)
}

/**
 * A shortcut for load(fresh = true, reset = false). Requests to load fresh data and preserve the old one until successful outcome.
 */
fun  Loading.refresh() = load(fresh = true, reset = false)

/**
 * A shortcut for load(fresh, reset = true). Requests to drop old data and load new one.
 * @param fresh indicates that fresh data is required. See [OrdinaryLoader.load] and [FlowLoader.load].
 */
fun  Loading.restart(fresh: Boolean = true) = load(fresh, reset = true)

/**
 * A shortcut for cancel(reset = true). Cancels loading and sets state to [Loading.State.Empty].
 */
fun  Loading.reset() = cancel(reset = true)

/**
 * Returns a current [Loading.State].
 */
val  Loading.state: Loading.State get() = stateFlow.value

/**
 * Returns [Loading.State.Data.data] if it is available or null otherwise
 */
val  Loading.dataOrNull: T? get() = state.dataOrNull

/**
 * Returns [Loading.State.Error.throwable] if it is available or null otherwise
 */
val Loading<*>.errorOrNull: Throwable? get() = state.errorOrNull

/**
 * Returns [Loading.State.Data.data] if it is available or null otherwise
 */
val  Loading.State.dataOrNull: T? get() = (this as? Loading.State.Data)?.data

/**
 * Returns [Loading.State.Error.throwable] if it is available or null otherwise
 */
val Loading.State<*>.errorOrNull: Throwable? get() = (this as? Loading.State.Error)?.throwable

/**
 * A helper method to handle [Loading.Event.Error].
 */
fun  Loading.handleErrors(
    scope: CoroutineScope,
    handler: (Loading.Event.Error) -> Unit
): Job {
    return eventFlow.filterIsInstance>()
        .onEach {
            handler(it)
        }
        .launchIn(scope)
}

/**
 * Returns new [Loading.State] of applying the given [transform] function to original [Loading.State.Data.data].
 */
fun  Loading.State.mapData(transform: (T) -> R): Loading.State {
    return when (this) {
        Loading.State.Empty -> Loading.State.Empty
        Loading.State.Loading -> Loading.State.Loading
        is Loading.State.Error -> Loading.State.Error(throwable)
        is Loading.State.Data -> Loading.State.Data(transform(data), refreshing)
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy