Last active
August 27, 2021 09:27
-
-
Save ConorGarry/8facf4c170528abb925e6ee4312d8984 to your computer and use it in GitHub Desktop.
Monad for handing UI state representation with a clean consumable API.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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