Skip to content

Instantly share code, notes, and snippets.

@brady-aiello
Last active November 14, 2023 22:20
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save brady-aiello/02fd6bbe152cd7a6746f3bc01d1f32a6 to your computer and use it in GitHub Desktop.
Save brady-aiello/02fd6bbe152cd7a6746f3bc01d1f32a6 to your computer and use it in GitHub Desktop.
A data state sealed class that treats Loading as a member of every state, rather than an exclusive state itself.
/*
This UiState model addresses the problems associated with treating Loading as its own state.
Loading in general is not an exclusive state.
You may be showing data, and fetching (Success + Loading).
You might show an error, and try to fetch the data again (Error + Loading)
The only time Loading is kind of its own thing is when the screen is first opened, and nothing is displayed yet.
So that case, InitialBlankLoading should be a separate state.
Here's what a flow in this would look like, fetching a list of messages:
1. I open the screen:
InitialBlankLoading(loading = true)
2. I fetch a list of messages:
still InitialBlankLoading(loading = true)
3. I get the list of the messages:
Success(loading = false)
4. I fetch messages again, keeping the previously loaded messages onscreen until it's refreshed:
Success(loading = true)
5. I receive the updated message list:
Success(loading = false)
6. I try to fetch the message list again, displaying the old data until it's refreshed:
Success(loading = true)
7. I get an error, and display the error UI:
Error(loading = false)
8. I fetch again, keeping the error UI on screen until we get a response:
Error(loading = true)
9. I receive a correct response, but I've deleted all my messages, so it's just an empty list.
Empty(loading = false)
The copyLoading() method helps us change state without losing data.
We don't want a flash of an empty screen every time we fetch it again.
*/
sealed interface UiState<out T: Any> {
val loading: Boolean
fun copyLoading(loading: Boolean): UiState<T>
}
/**
* We've successfully gotten the data we need for this view.
*/
data class Success<out T: Any>(
val data: T,
override val loading: Boolean
) : UiState<T> {
override fun copyLoading(loading: Boolean): UiState<T> {
return this.copy(loading = loading)
}
}
/**
* There was a problem getting the data for this view.
*/
data class Error<out T: Any>(
val exception: Exception,
override val loading: Boolean
): UiState<T> {
override fun copyLoading(loading: Boolean): UiState<T> {
return this.copy(loading = loading)
}
}
/**
* We got a response, but there's nothing there, eg. an empty list.
*/
data class Empty<out T: Any>(
override val loading: Boolean
): UiState<T> {
override fun copyLoading(loading: Boolean): UiState<T> {
return this.copy(loading = loading)
}
}
/**
* Only for opening the page for the first time, fetching data before any UI has previously loaded.
*/
data class InitialBlankLoading<out T: Any>(
override val loading: Boolean = true
): UiState<T> {
override fun copyLoading(loading: Boolean): UiState<T> {
return this.copy(loading = loading)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment