Side Effects in Jetpack Compose: Demystifying the Concept

ยท

4 min read

Play this article

As Seen In - jetc.dev Newsletter Issue #189

What is Side Effect?

Composable functions are typically used to render UI elements. However, there are cases where composable functions need to execute code that is not related to the UI like sending analytics events or navigating to another screen given a certain state condition.

For those scenarios, you don't want to put those codes inside a Compose function due to the unpredictable composition.

Jetpack Compose Side Effect API

Jetpack Compose provides a number of APIs for handling side effects in your composable functions. These APIs allow you to encapsulate your side effects and make your code more readable and maintainable.

LaunchedEffect

LaunchedEffect launches a coroutine to perform a long-running operation, such as a network request. The coroutine will be canceled when the composable is no longer used.

LaunchedEffect takes two parameters:

  • key: A key that is used to identify the LaunchedEffect. If the key changes, the existing coroutine will be canceled and a new coroutine will be launched.

  • coroutineScope: A coroutine scope in which the side effect will be executed.

@Composable
fun UserProfile(userId: String) {
    val userDetailsState = rememberMutableState { UserDetails() }

    LaunchedEffect(key = userId) {
        val userDetails = fetchUserDetails(userId)
        userDetailsState.value = userDetails
    }

    // Display the user's name
    Text(userDetailsState.value.name)
}

In this example, the LaunchedEffect will be launched when the composable function is first displayed. The LaunchedEffect will fetch the user's name from the network and store it in the userDetailsState variable. Once the user's name has been fetched, the composable function will be recomposed with the updated userDetailsState variable. This will cause the user's name to be displayed on the screen.

LaunchedEffect is a powerful tool for performing side effects in Jetpack Compose. It allows you to perform asynchronous operations without blocking the composable function. This makes it a good choice for performing tasks such as fetching data from the network, playing sounds, and starting timers.

DisposableEffect

DisposableEffect registers a callback to be invoked when the composable is no longer used. This can be used to clean up any resources that were allocated by the composable.

DisposableEffect takes two parameters:

  • key: A key that is used to identify the DisposableEffect. If the key changes, the existing DisposableEffect will be disposed of and a new DisposableEffect will be created.

  • effect: A block of code that performs the side effect. The onDispose block will be called when the composable leaves the Composition to clean up any resources that were used by the side effect.

@Composable
fun MyComposable() {
    val button = Button(onClick = { })

    DisposableEffect(key = button) {
        val eventListener = object : OnClickListener {
            override fun onClick(v: View) {
                // Do something when the button is clicked
            }
        }

        button.setOnClickListener(eventListener)

        onDispose {
            button.setOnClickListener(null)
        }
    }

    // Display the button
    button
}

In this example, the DisposableEffect will register the event listener with the button when the composable function is first displayed. The DisposableEffect will also unregister the event listener when the composable function leaves the Composition to prevent memory leaks.

DisposableEffect can also be used to clean up other types of resources, such as animations and database connections. It is a powerful tool for managing resources in Jetpack Compose.

SideEffect

SideEffect performs a side effect, such as logging or sending a notification. Side effects are not cleaned up when the composable is no longer used.

SideEffect takes a single parameter:

  • effect: A block of code that performs the side effect.
@Composable
fun MyComposable() {
    val counter = remember { mutableStateOf(0) }

    SideEffect {
        counter.value += 1
    }

    // Display the counter value
    Text("Counter: ${counter.value}")
}

In this example, the SideEffect will update the counter variable whenever the composable function is recomposed. This will cause the composable function to be recomposed again, which will display the updated counter value on the screen.

Note for SideEffect

It is important to be careful when using SideEffect, as it can lead to performance problems if it is used incorrectly. For example, if you use SideEffect to update a shared variable that is used by multiple composable functions, this can lead to a cascading effect where all of the composable functions are recomposed unnecessarily.

It is generally best to use SideEffect sparingly and only for side effects that are absolutely necessary. If you need to perform a side effect that is more complex or asynchronous, you should consider using LaunchedEffect instead.

ย