I'm working on a Composable that updates text after getting an HTTP response, and I'm trying to figure out a way to structure my Composables so that when recomposition occurs, it doesn't repeat the loadJson
call.
@Composable
fun Schedule() {
var textToShow by remember { mutableStateOf("loading...") }
RemoteConfig.loadJson("schedule.api",
onSuccess = { routes ->
textToShow = routes[1].jsonObject.toString()
},
onError = { e ->
textToShow = "Failed to load bus schedule.\n($e)"
}
)
Text(textToShow)
}
I'm using remember
to prevent recomposition if the text doesn't change, but loadJson
still gets called twice-- once the first time the Composable is drawn, and again when textToShow
's value is updated. If the HTTP response was different every time, recomposition would be triggered indefinitely. I believe it boils down to these being in the same scope, but I'm having a hard time restructuring it to do what I want. I'd love to see examples of better approaches.
You could try to use a LaunchedEffect
. It allows executing asynchronous code at the moment that the Composable enters into the composition. It is not called again at recompositions.
You can adjust your code like this:
@Composable
fun Schedule() {
var textToShow by remember { mutableStateOf("loading...") }
LaunchedEffect(Unit) { // by providing Unit, it only will be executed once
// this is a Coroutine Scope, so you could also call suspend functions here
RemoteConfig.loadJson("schedule.api",
onSuccess = { routes ->
textToShow = routes[1].jsonObject.toString()
},
onError = { e ->
textToShow = "Failed to load bus schedule.\n($e)"
}
)
}
Text(textToShow)
}
As a side note, you could consider to delegate the logic to a ViewModel instead, and only pass the UI state (loading, success, error) to the Composable.