본문 바로가기

안드로이드/Compose

Side Effect

Compose에서 SideEffect는 Composable 함수 내에서 UI와 별개로 추가적인 작업이 필요한 경우에 사용하는 함수로,

데이터 업데이트, 네트워크 통신 등 Composable 함수 외적인 작업들을 예측 가능한 방식으로 제어하기 위해 사용한다.

  • SideEffect
  • LaunchedEffect
  • DisposableEffect

만일 SideEffect가 아닌 Composable 함수 내에 그냥 작성을 하게 된다면 Recomposable 될 때마다 다시 실행하지 않아도 되는 코드들이 모두 실행되어 불필요한 자원이 소모될 것이다.

따라서 특정 상황에서만 작업이 필요한 경우, 한 번만 작업하면 되는 경우 또는 UI와 별개의 작업이 필요한 경우 등 다양한 작업을 UI와 분리하여 성능과 가독성을 높이고 유지보수를 더욱 용이하게 할 수 있다.

 

1. SideEffect

  • SideEffect가 선언되어 있는 Composable 함수가 Recomposition 될 때마다 실행된다.
  • 비동기로 동작하지 않는다.
  • Recomposition 이후에 실행된다. (화면 구성 이후 마지막에 실행됨)
@Composable
fun ChildCompose(
    num: () -> Int,
    onClick: () -> Unit
) {
    SideEffect {
        Log.e("SideEffect", "DO!")
    }
    Log.e("ChildCompose", "Recomposition Start")
    Text(text = "First - ${num()}")

    Button(onClick = { onClick() }) {
        Text(text = "First Text Change")
    }
    Log.e("ChildCompose", "Recomposition End")
}

 

 

2. LaunchedEffect

fun LaunchedEffect(
    key1: Any?,
    block: suspend CoroutineScope.() -> Unit
)

 

  • Composition 때만 동작하거나(Recomposition 시 동작하지 않음), 특정 값의 변경이 이루어질 때만 동작한다.
    • LaunchedEffect(Unit) : 단 한 번만 실행
    • LaunchedEffect(key): Key 값이 변경될 때마다 실행
  • CoroutineScope로 이루어져 비동기 작업이 가능하다.
  • Key 설정 시 Key의 값이 변경되면 코루틴은 취소되고 다시 실행된다.
  • Key는 여러 개 설정할 수 있다.
  • Composition에서 벗어나면 코루틴은 취소된다.
@Composable
fun ChildCompose(
    timerState: () -> Boolean,
    onClick: () -> Unit
) {
    if(timerState()){
        Timer()
    }

    Button(onClick = { onClick() }) {
        Text(text = "On/Off")
    }
}

@Composable
fun Timer(){
    var time by remember{ mutableIntStateOf(0) }
    LaunchedEffect(Unit) {
        while(true){
            delay(1000L)
            time++
            Log.e("Timer", time.toString())
        }
    }

    Text("Time : $time")
}

5초 이후에 버튼을 클릭하여 TimerState를 false로 바꾸면 코루틴이 취소되어 더이상 로그가 찍히지 않는다.

 

3. DisposableEffect

DisposableEffect는 다른 Effect와는 다르게 내부에 onDispose 함수를 호출 할 수 있다.

onDispose 함수는 Composition이 종료될 때 호출되기 때문에 Composition 종료 시 추가적인 작업이 필요한 경우에 적합하다.

(Activity Lifecycle의 onDestroy와 같은 기능이라고 생각하면 좋을 것 같습니다.)

  • CoroutineScope로 이루어져 있지 않다.
  • onDispose 함수를 통해 Composition 종료 시의 작업을 처리할 수 있다.
  • LaunchedEffect와 같이 Key를 설정할 수 있다.
@Composable
fun Timer(){
    var time by remember{ mutableIntStateOf(0) }
    DisposableEffect(Unit) {
        Log.e("DisposableEffect", "Start")

        onDispose {
            Log.e("DisposableEffect", "End")
        }
    }

    LaunchedEffect(Unit) {
        while(true){
            delay(1000L)
            time++
            Log.e("Timer", time.toString())
        }
    }

    Text("Time : $time")
}