Navigating Effect Handlers and Side Effects in Jetpack Compose: A Practical Introduction
Tags: kotlin, androiddev, jetpackcompose, android
Published: 2024-01-26 01:02:59

### What are Effects Handlers?
Effects handlers in Jetpack Compose are used to handle side effects in composable functions. To understand effects handlers better, we need to understand what side effects are.

A side effect is a situation where an asynchronous code that operates outside of the scope of a composable alters the state of the composable without taking into account the lifecycle of the composable.

For instance, initiating a network request where the response updates the composable state, triggering an animation sequence, or modifying composable properties in response to changes in state management are examples of actions that could lead to side effects.

These side effects occur as the composable interacts with asynchronous operations or external state changes that are not linked to user interface rendering.

To handle these side effects we make use of effects handlers. Let's delve into the different types of effects handlers on Jetpack Compose.

## LaunchedEffect
It is a widely used effect handler in Jetpack Compose. It enables you to execute asynchronous operations, manage external data sources, and update the user interface based on the outcomes. Importantly, it ensures that these operations are tied to the lifecycle of the composable and are properly canceled when the composable is disposed of:
```kotlin
@Composable
fun MyScreen() {
   var data by remember { mutableStateOf("") }

   LaunchedEffect(key1 = data) {
       data = fetchDataFromApi() 
   }

   if (data.isNotEmpty()) {
       Text(text = data)
   } else {
       CircularProgressIndicator()
   }
}
```
In this example, _LaunchedEffect_ is used to fetch data from an API when the MyScreen composable is first composed. Here’s what’s going on:
_LaunchedEffect_ launches a coroutine when MyScreen composable is drawn on the screen for the first time
The key1 = data parameter is used to tell the launch effect when to cancel and relaunch the coroutine. If the data variable changes _launchedEffect_ will cancel the current coroutine and start a new one
Inside the launch effect, _fetchDataFromApi()_ will be called which is a hypothetical suspend function that fetches data from an API. This could be anything that requires asynchronous computation like fetching data from a database or fetching over a network.
The result of _fetchDataFromApi_ is then stored in the data variable this is observed by _MyScreen_ composable.
If the data is not empty, a text composable is displayed with the data. Otherwise, a _CircularProgressIndicator_ is shown indicating that data is still loading.
This is a simple example, but it demonstrates how _launchedEffects_ can be used for handling asynchronous tasks in a composable lifecycle-aware manner. It ensures that the coroutine is cancelled when the composable is disposed of to avoid memory leaks. It also handles recomposition when the data changes ensuring the UI is up to date.

## rememberCoroutinScope
With _rememberCoroutineScope_, we can get access to a coroutine scope that is life cycle aware within a composable. As soon as the composable leaves the composition, the coroutine scope is cancelled. 
```kotlin
@Composable
fun myScreen() {
   val scope = rememberCoroutineScope()
  
   Button(onClick = {
       scope.launch {
           delay(1000L)
           println("hello world")
       }
   }) {

   }
}
```
Inside _myScreen_ we call _rememberCoroutinScope_. This creates a coroutine scope tied to the lifecycle of the composable. 
We then define a button composable. The onClick parameter is a callback that is executed when the button is clicked.
Inside onClick we launch a new coroutine scope that we remembered earlier. This coroutine scope waits for 1 second and then prints “Hello World” to the console.

It’s important to note that this is typically used in callbacks, such as in an onClick event in our code. It’s not directly related to the actual composables. For instance, it could be used to initiate a network call when a button is clicked.
However, in an ideal scenario, this would be managed in the ViewModel, as you already have a scope defined in the ViewModel
## rememberUpdatedState
_rememberUpdatedState_ is a function that allows you to reference a value in an effect that shouldn't restart if the value changes. it is used to remember a mutable state and update its value on each recomposition of the remember state call:
```kotlin
@Composable
fun myScreen(
   onTimeout: () -> Unit
) {
   val updatedTimeout by rememberUpdatedState(newValue = onTimeout)
   LaunchedEffect(key1 = true){
       delay(3000L)
       updatedTimeout
   }
}
```
_myScreen_ composable takes a lambda _onTimeout_ as a parameter. This lambda is called when a certain condition is met.
Inside _myScreen_, we call _rememberUpdatedState_. This function returns a mutable state object that holds a reference on _onTimeout_ and updates the reference whenever _onTimeout_ changes
Inside the _launchedEffect_ we first delay for 3000 milliseconds. And then call updated timeout. This means that after 3000 milliseconds the _ontimeout_ lambda passed to _myScreen_ will be invoked
If _onTimeout_ changes during recomposition of the composable _updatedTimeout_ will always hold the latest version of _onTimeout_.

It is important to note that _rememberUpdatedState_ should be used when parameters or values computed during composition are referenced by a long-lived lambda object or object expression.
## DisposableEffect
Disposable effects are basically for managing side effects that require clean-up when the composable that created them is removed from composition or when its key changes. It is the ability to clean up these side effects when they are no longer needed:
```kotlin
@Composable
fun myScreen() {
   val lifecycleOwner = LocalLifecycleOwner.current
   DisposableEffect(key1 = lifecycleOwner){
       val observer = LifecycleEventObserver{ _, event ->
           if (event == Lifecycle.Event.ON_PAUSE){
               println("on pause caled")
           }
           lifecycleOwner.lifecycle.addObserver(observer)

           onDispose {
               lifecycleOwner.lifecycle.removeObserver(observer)
           }

       }
   }
}
```
We define a composable _myScreen_
Inside _myScreen_, it fetches the current _LifeCycleOwner_ using _LocalLifeCycleOwner.current_. This provides a way to interact with the lifecycle of the activity.
A _DisposableEffect_ is used to perform side effects related to the lifeCycle. It has _lifeCycleOwner_ as its key, meaning the effect will re-execute and dispose of/recreate resources if the _lifeCycleOwner_ changes.
Within the Disposable effect an observer is created for lifecycle events (LifecycleEventObserver).
The observer is added to the _lifecycleowner.lifecycle_ to receive lifecycle events.
The _onDispose_ block is crucial for cleaning up resources when the effect is disposed of. It is responsible for removing the observer and ensuring that the observer is unregistered to avoid potential memory leaks and unwanted actions after the composable is no longer in the composition.
## produceState
The purpose of this function is to produce some type of state that changes over time. It initiates a coroutine that is confined to the composition, allowing you to update the state’s content using the value property. This function is beneficial for handling data that changes periodically(similar to flow) or in response to user interactions: 
```kotlin
@Composable
fun myScreen(countUpTo: Int): State<Int> {
   return produceState(initialValue = 0){
       while (value < countUpTo){
           delay(1000L)
           value++
       }
   }
}
```
_myScreen_ is a composable function that takes an integer _countUpTo_, and returns a _state<Int>_
Inside _myScreen_ produce state is called with an initial value of 0. This function returns a state object that is remembered across recompositions
The produce state block contains a while loop that increments the value of the state by every 1 second as long the value is less that _countUpTo_
The value inside _produceState_ refers to the current value of the state. You can read it and update it.
The updated value is returned as state<Int> from _myScreen()_ 
## derivedStateOf
It is a function that allows you to create a new state function that is derived from one or more state objects. The primary goal of the _derivedStateOf_ function is to create a derived state which is a state value computed based on other state values. Whenever the underlying state value changes the derived state changes. 
```kotlin
@Composable
fun myScreen() {
   var counter by remember{
       mutableIntStateOf(0)
   }

   val counterText by remember{
       derivedStateOf {
           "the counter is $counter"
       }
   }

   Button(onClick = {counter++}) {
       Text(text = counterText)
      
   }
}
```
In this example, _counterText_ is a derived state that depends on the counter. Every time the counter changes _counterText_ is automatically recalculated. 
However, the Ui is only recomposed when _counterText_ changes and not when counter changes. 
It improves performance by reducing unnecessary recompositions.
It is often used in scenarios where you have a complex state that depends on one or more states.
## snapShotFlow
With the _snapshotflow_ function, think of it like you are a photographer capturing a series of snapshots of a scene that is constantly changing. You want to focus only on parts that have changed, rather than taking redundant photos of everything.
_snapshotFlow_ enables you to do this with state values in your composables.
It creates a flow that emits snapshots of a state whenever it changes focusing only on the differences between previous and the current snapshots thereby optimizing data flow.
```kotlin
@Composable
fun MyScreen(viewModel: MyViewModel) {
   val displayItemState = viewModel.displayItemState

   LaunchedEffect(key1 = displayItemState) {
       snapshotFlow { displayItemState }
           .distinctUntilChanged()
           .filter { it.viewIntent != null }
           .collectLatest { displayItemState ->
               context.startActivity(displayItemState.viewIntent)
           }
   }
}
```
_snapshotFlow_ is called with a block of code that reads from _displayItemState_. This creates a Flow that emits a new value every time _displayItemState_ changes.
The Flow returned by _snapshotFlow_ is collected inside a _LaunchedEffect_. As we discussed earlier _LaunchedEffect_ is a composable in Jetpack Compose that can be used to perform side-effects, such as collecting from a Flow, in response to state changes. 
When _displayItemState_ changes, _snapshotFlow_ emits a new value. This value is then collected inside the _LaunchedEffect_, triggering any side-effects that depend on the latest value of _displayItemState_.

## Conclusion
In conclusion, understanding and effectively managing side effects is a crucial aspect of developing robust and maintainable software. Side effects are inevitable in most applications, but with the right tools and strategies, we can ensure they don’t compromise the predictability and reliability of our code.
Remember, the key to effectively using these tools is understanding the lifecycle of your composables and how state changes propagate through your application. With this knowledge, you can write code that’s not only easier to understand and maintain but also more efficient and responsive.
So go forth, compose with confidence, and embrace the power of controlled side effects!😊