Skip to content

Instantly share code, notes, and snippets.

@ConorGarry
Last active August 27, 2021 09:27
Show Gist options
  • Select an option

  • Save ConorGarry/8facf4c170528abb925e6ee4312d8984 to your computer and use it in GitHub Desktop.

Select an option

Save ConorGarry/8facf4c170528abb925e6ee4312d8984 to your computer and use it in GitHub Desktop.
Monad for handing UI state representation with a clean consumable API.
sealed class UiState<out T> {
fun interface SuccessHandler<T> {
fun invoke(data: T)
}
fun interface FailureHandler {
fun invoke(error: Throwable)
}
open fun onUiState(
onSuccess: SuccessHandler<in T>,
onError: FailureHandler,
onLoad: (() -> Unit)? = null,
onNone: (() -> Unit)? = null,
) {
/* Override as needed. */
}
object None : UiState<Nothing>() {
override fun onUiState(
onSuccess: SuccessHandler<in Nothing>,
onError: FailureHandler,
onLoad: (() -> Unit)?,
onNone: (() -> Unit)?,
) {
onNone?.run { invoke() }
}
}
object Loading : UiState<Nothing>() {
override fun onUiState(
onSuccess: SuccessHandler<in Nothing>,
onError: FailureHandler,
onLoad: (() -> Unit)?,
onNone: (() -> Unit)?,
) {
onLoad?.run { invoke() }
}
}
data class Success<T>(val data: T) : UiState<T>() {
override fun onUiState(
onSuccess: SuccessHandler<in T>,
onError: FailureHandler,
onLoad: (() -> Unit)?,
onNone: (() -> Unit)?,
) {
onSuccess.invoke(data)
}
}
class Error<T>(val exception: Throwable) : UiState<T>() {
override fun onUiState(
onSuccess: SuccessHandler<in T>,
onError: FailureHandler,
onLoad: (() -> Unit)?,
onNone: (() -> Unit)?,
) {
onError.invoke(exception)
}
}
}
// Extension functions to alleviate wrapper boilerplate and indentation.
fun <T> T.stateSuccess(): UiState.Success<T> = UiState.Success(this)
fun <T> Throwable.stateError(): UiState.Error<T> = UiState.Error(this)
fun main() {
val result = "Hello World!".stateSuccess()
// or val result = Exception("Oops!").stateError<String>() / UiState.Loading / UiState.None
// Simple Success / Error, maybe you don't need Loading state.
// Success is first closure, Error is second. Similar to try/catch, Rx subscription, runCatching.
result.onUiState(
{ println("Success: $it") },
{ it.printStackTrace() }
)
// With a Loading state.
result.onUiState(
{ println("Success: $it") },
{ it.printStackTrace() },
{ println("Show a loading UI.") }
)
// Same as above, but maybe you want to put loading first, then you need labels,
// where you can put them in any arbitrary order.
result.onUiState(
onLoad = { println("Show a loading UI.") },
onSuccess = { println("Success: $it") },
onError = { it.printStackTrace() }
)
// All states accounted for.
result.onUiState(
onLoad = { println("Show a loading UI.") },
onSuccess = { println("Success: $it") },
onError = { it.printStackTrace() },
onNone = { println("Reset UI to initial state.") }
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment