https://kotlinlang.org logo
#compose
Title
# compose
g

grandstaish

05/20/2020, 3:45 PM
Suppose I wanted to draw a loading spinner, but only if a load is taking longer than X milliseconds to complete (to avoid jarring/sudden UI changes for really quick async loads). Once the loading spinner is showing however, I want it to stick around for Y milliseconds before rendering the new data. Is this ux possible in Compose?
v

Vinay Gaba

05/20/2020, 4:50 PM
If I understood your question correctly, this should absolutely be possible to do with Compose. I’m assuming you have this information available in order to make the decision of what to show -
load is taking longer than X milliseconds to complete
g

grandstaish

05/20/2020, 4:57 PM
Ideally, I’d want this be a UI concern only, and not modelled anywhere in my app’s state. I.e. as soon as the data is loaded, it is in my app’s state. But visually, compose would provide a way for me to say keep rendering my loading component for a bit because it only just started showing. That way I could have a reusable loading composable function that encapsulates all of this functionality and i can reuse throughout my app. So that composable function would have to track how long it’s been rendering for
l

Leland Richardson [G]

05/20/2020, 5:17 PM
this would be a fun use case for
launchInComposition
🙂
1
👀 4
a

Adam Powell

05/20/2020, 5:56 PM
g

grandstaish

05/20/2020, 6:56 PM
Wow I can't wait for this 😍
a

Adam Powell

05/20/2020, 9:20 PM
and it just merged 🙂
👏 6
t

Timo Drick

05/21/2020, 1:13 AM
I implemented exactly this behavior with the following short code snipped in Compose (the state is Start and after 500ms Loading and than sucess or error:
Copy code
sealed class LoadingState<out T: Any> {
    object Start: LoadingState<Nothing>()
    object Loading : LoadingState<Nothing>()
    class Error(val error: Throwable): LoadingState<Nothing>()
    class Success<T: Any>(val data: T): LoadingState<T>()
}

@Composable
fun <T: Any> loadingStateFor(vararg inputs: Any?, initBlock: () -> LoadingState<T>, disposeBlock: () -> Unit = {}, loadingBlock: suspend CoroutineScope.() -> T): LoadingState<T> {
    var state by stateFor(*inputs, init = initBlock)

    onCommit(*inputs) {
        val job = if (state !is LoadingState.Success) {
            CoroutineScope(Dispatchers.Main.immediate).launch {
                val loadingSpinnerDelay = async {
                    delay(500)
                    state = LoadingState.Loading
                }
                state = try {
                    LoadingState.Success(loadingBlock())
                } catch (err: Throwable) {
                    LoadingState.Error(err)
                } finally {
                    loadingSpinnerDelay.cancelAndJoin()
                }
            }
        } else null
        onDispose {
            job?.cancel()
            disposeBlock()
        }
    }
    return state
}