CLOVIS
03/11/2021, 2:18 PM@Composable
functions should not block nor suspend, and says that an event (a button click, for example) should be transmitted ‘up' and dealt with there. I don't really understand what this means or how it works in practice, and I can't find a page that describes that...
Assuming I have a simple function (that simulates a network call):
suspend fun networkGet(id: Int): String {
delay(2000)
return "some interesting value"
}
I would want to have a UI that has a button to launch the request, which then displays the results (whenever they arrive).
I guess the Composable
function should look like this:
@Composable
fun SomeView(results: List<String>, onRequest: (Int) -> Unit) {
Column {
Button(onClick = { onRequest(5) } {
Text("Load next")
}
for (result in results) {
Text(result)
}
}
}
So far it makes sense to me, but I don't understand how the call site would handle the event.Dominik wuttke
03/11/2021, 2:28 PMTimo Drick
03/11/2021, 2:39 PMsealed 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> = { LoadingState.Start },
loadingBlock: suspend CoroutineScope.() -> T): LoadingState<T> {
var state by remember(*inputs) { mutableStateOf(initBlock()) }
if (state !is LoadingState.Success) {
LaunchedEffect(*inputs) {
val loadingSpinnerDelay = async {
delay(300)
state = LoadingState.Loading
}
state = try {
LoadingState.Success(loadingBlock())
} catch (err: Exception) {
LoadingState.Error(err)
} finally {
loadingSpinnerDelay.cancelAndJoin()
}
}
}
return state
}
Usage:
var retryCounter by remember { mutableStateOf(0) }
val imageState = loadingStateFor(url, retryCounter) {
//execute your suspend function here
}
Crossfade(imageState) { state ->
when (state) {
is LoadingState.Start -> Box(
Modifier
.fillMaxSize()
.background(backgroundColor))
is LoadingState.Loading -> LoadingBox(
Modifier
.fillMaxSize()
.background(backgroundColor))
is LoadingState.Success -> Image(bitmap = state.data, contentDescription = "Image", modifier = Modifier.fillMaxSize(), contentScale = ContentScale.Crop)
is LoadingState.Error -> ErrorBox(
Modifier
.fillMaxSize()
.background(backgroundColor),
message = state.error.message, onRetry = { retryCounter++ })
}
}
CLOVIS
03/11/2021, 4:55 PMTimo Drick
03/11/2021, 4:58 PMTimo Drick
03/11/2021, 5:10 PMsealed class LoadingState<out T: Any> {
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?,
loadingBlock: suspend CoroutineScope.() -> T): LoadingState<T> {
var state by remember(*inputs) { mutableStateOf<LoadingState<T>>(LoadingState.Loading) }
LaunchedEffect(*inputs) {
state = try {
LoadingState.Success(loadingBlock())
} catch (err: Exception) {
LoadingState.Error(err)
}
}
return state
}
data class CustomData(val test: String)
suspend fun dummyLoader() = withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
delay(1000)
CustomData("Loaded data")
}
@Composable
fun exampleUsage() {
var retryCounter by remember { mutableStateOf(0) }
val imageState = loadingStateFor(retryCounter) {
dummyLoader()
}
when (val state = imageState) {
is LoadingState.Loading -> LoadingBox()
is LoadingState.Success -> YourDataScreen(state.data)
is LoadingState.Error -> ErrorBox(message = state.error.message, onRetry = { retryCounter++ })
}
}
@Composable
fun YourDataScreen(data: CustomData) {
//....
}
Timo Drick
03/11/2021, 5:11 PMTimo Drick
03/11/2021, 5:18 PMTimo Drick
03/11/2021, 5:20 PMCLOVIS
03/11/2021, 9:51 PM