
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