Side Effects: Launched Effect - devrath/ComposeAlchemy GitHub Wiki
- The launched Effect receives
a key
or acollection of keys
- The keys are nothing but a compose state.
- The keys decide when the composable function inside the
Launched Effect
is called again. -
Launched Effect
is a composable function but it has no UI. - Also the previous scope is cancelled when new is invoked before the previous scope completes.
LaunchedEffect(counterState) {
// This block is a co-routine scope
// This is invoked when the compose state changes that is passed as a key
}
- Also sometimes we just want to invoke it only once.
- We can just pass a constant as a key
LaunchedEffect(Unit) {
// Block of code
}
- Observee by clicking the button continuously, The previous block is cancelled and new is executed.
@Composable
fun LaunchedEffectDemo2Screen(modifier: Modifier = Modifier) {
var counterState by remember { mutableIntStateOf(0) }
val snackbarHostState = remember { SnackbarHostState() }
LaunchedEffect(counterState) {
if (counterState % 2 == 0) {
snackbarHostState.showSnackbar(" $counterState --> It is a even number")
}
}
Scaffold(
modifier = Modifier.fillMaxSize(),
snackbarHost = { SnackbarHost(hostState = snackbarHostState) }
) { padding ->
Box(
modifier = Modifier
.fillMaxSize()
.padding(padding),
contentAlignment = Alignment.Center
) {
Button(
onClick = { counterState++ }
) {
Text(text = "Current Value -> $counterState")
}
}
}
}
Demo
Observation
- Launched effect is a composable function, That executes the side-effect in a separate co-routine scope.
- This effect is useful for executing operations that are long-running and that might take more time to execute such as network calls or animations without blocking the UI thread.
- Use cases for launched effect
- Fetching Data from a Network
- Performing Image Processing
- Updating a Database
- We can use the
key
field in the composable ofLaunchedState
to prevent/control the composition, Because sometimes the long-running operation might take longer time to execute, and in between, we need to prevent unnecessary composition.
output
// <----------- Initially when the composable is composed for the first time ----------->
Root composable is composed
Column composable is composed
Column composable (parent of button) is composed
List is displayed
LaunchedEffect is invoked :--> Before fetching the data
Before calling API
Root composable is composed
Column composable is composed
Column composable (parent of button) is composed
Loader is shown
Inside API call
After calling API
LaunchedEffect is invoked :--> After fetching the data
Root composable is composed
Column composable is composed
Column composable (parent of button) is composed
List is displayed
// <----------- When we clicked the button to fetch data again ----------->
Button click invoked
Root composable is composed
Column composable is composed
Column composable (parent of button) is composed
Loader is shown
Inside API call
Root composable is composed
Column composable is composed
Column composable (parent of button) is composed
List is displayed
@Composable
fun TypeLaunchedEffect(navController: NavHostController) {
val isLoaderDisplayed = remember { mutableStateOf(false) }
val data = remember { mutableStateOf(listOf<String>()) }
println("Root composable is composed")
Column(
modifier = Modifier
.fillMaxSize()
.padding(10.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
) {
println("Column composable is composed")
// This side effect is launched only when the boolean value is true(Initially its false)
LaunchedEffect(key1 = isLoaderDisplayed.value){
println("LaunchedEffect is invoked :--> Before fetching the data")
// Perform a long running operation that takes time
data.value = fetchData()
println("LaunchedEffect is invoked :--> After fetching the data")
// Reset to false
isLoaderDisplayed.value = false;
}
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
) {
println("Column composable (parent of button) is composed")
AppButton(text = "Fetch Data"){
isLoaderDisplayed.value = true
println("Button click invoked")
}
if (isLoaderDisplayed.value) {
// Show a loading indicator
CircularProgressIndicator()
println("Loader is shown")
} else {
// Show the data
LazyColumn {
items(data.value.size) { index ->
Text(text = data.value[index])
}
}
println("List is displayed")
}
}
}
}
// Simulate a network call by suspending the coroutine for 2 seconds
private suspend fun fetchData(): List<String> {
// Simulate a network delay
delay(2000)
return listOf("Item 1", "Item 2", "Item 3", "Item 4", "Item 5",)
}