Search code examples
kotlinandroid-jetpack-composecompose-recomposition

How can I structure a Composable to only trigger recomposition once?


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.


Solution

  • 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.