Compose Effect란?

Arakene·2024년 4월 15일

SideEffect 정의

composable function의 스코프밖에서

  • recomposition이 일어날 때
  • recomposition이 다른 순서로 실행횔 때
  • recomposition이 중간에 제거될 수 있을 때

모든 사이드 이펙트는 사라져야하는가?

아니다.
사이드 이펙트가 반드시 필요한 경우가 있다. 예를 들어 특정 이벤트를 통해서 스낵바를 보여주거나 네트워크 통신 (== 서버와 통신)해서 UI 컨트롤을 해야한다거나 flow collect, 다른화면으로 특정값일 때 이동 등이 있다.
이런 액션들은 반드시 composable의 생명주기에 맞춰 진행되어야 한다. 이번 글에서는 compose에서 제공해주는 sideEffect 처리를 적고자 한다.

LaunchedEffect

LaunchedEffect는 컴포지션을 시작할 때 호출하게된다. 컴포지션이 끝나는 경우 실행중이던 작업은 종료되게 된다. 만약 인자로 넘겨준 키값중 단 하나라도 변경된다면 실행중이던 작업을 정지하고 새로 작업을 시작하게 된다.
내부는 코루틴으로 구성되어 있으며 job을 통해 작업을 취소하는 것을 확인할 수 있다.

DisposableEffect

위 LaunchedEffect와 비슷하게 구성되지만 key값이 변경되어 작업을 재실행하기전, 혹은 컴포지션이 종료되어 effect자체가 종료될 때 onDispose 콜백을 실행시킨다. 주로 작업 종료 후 클린업이 필요한 경우 사용한다.
이전 글에서 다뤘던 LifecycleStartEffect같은 경우에도 DisposableEffect를 통해 등록했던 옵저버를 제거하는 것을 확인할 수 있다.

derivedStateOf

보통 state나 composable의 인풋이 변경되면 매번 리컴포지션이 일어나는데 비밀번호 입력 시 유효성검사를 수행한다고하면 비밀번호 입력 때 마다 리컴포지션이 일어날 수도 있다.
이런걸 방지하기 위해서 derivedStateOf가 존재한다.
아래 애니메이션을 한번 보면 명확히 이해가 된다.
이미지출처

rememberCoroutineScope

composable 밖에서 함수를 실행시켜야하지만 composition이 종료되면 작업이 캔슬되기를 원한다면 scope를 할당해서 사용하면된다.

ModalBottomSheetLayout에서 바텀시트를 show/hide할 때, Pager에서 페이지 이동시킬 때 사용한다.

rememberUpdatedState

LaunchedEffect, DisposableEffect는 키 값중 하나만 변경되어도 재실행이된다. 하지만 값은 항상 변경되어서 최신데이터를 유지하지만 effect를 재실행하고 싶지 않은 경우 rememberUpdatedState를 사용하면 된다.

SideEffect

compose를 사용하지 않는 함수를 composeState를 따라가도록 수정해준다.
Fisebase Analytics 또는 다른 로그 수집용할 때 composition이 일어날 때 마다 compose함수들 처럼 작업을 실행하도록 한다.

produceState

Sealed Class처럼 상태가 나눠져있지만 compose state를 사용하지 않는 코드를 Compose State처럼 사용할 수 있도록 해준다. Flow, LiveData에서 observeAsState()처럼 사용할 수 있도록 기능을 제공해준다 라고만 이해했다.
공식문서 예제

@Composable kotlin
fun loadNetworkImage(
    url: String,
    imageRepository: ImageRepository = ImageRepository()
): State<Result<Image>> {

    // Creates a State<T> with Result.Loading as initial value
    // If either `url` or `imageRepository` changes, the running producer
    // will cancel and will be re-launched with the new inputs.
    return produceState<Result<Image>>(initialValue = Result.Loading, url, imageRepository) {

        // In a coroutine, can make suspend calls
        val image = imageRepository.load(url)

        // Update State with either an Error or Success result.
        // This will trigger a recomposition where this State is read
        value = if (image == null) {
            Result.Error
        } else {
            Result.Success(image)
        }
    }
}
profile
안녕하세요 삽질하는걸 좋아하는 4년차 안드로이드 개발자입니다.

0개의 댓글