Grusie 안드로이드 개발 기술 블로그

[Android] 컴포즈 사이드 이펙트 - SideEffect, LaunchedEffect, DisposableEffect 본문

안드로이드 개발/컴포즈

[Android] 컴포즈 사이드 이펙트 - SideEffect, LaunchedEffect, DisposableEffect

grusie 2024. 3. 18. 22:04
728x90
반응형
SMALL

JetPack의 Compose를 활용하여 UI 개발을 하던 중 사용하는 사이드 이펙트들이 궁금해서 공부 해보려고 한다.

 

 

사이드 이펙트란?

- 사이드 이펙드는 UI에서 발생하는 Effect들을 효율적으로 관리 할 수 있는 함수이다.

- JetPack Compose에서 사이드 이펙트의 목적은 제어되고 예측 가능한 방식으로 컴포저블 함수 밖에서 앱의 상태를 변경하는 non-UI 관련 작업을 실행 할 수 있도록 하는 것이다.

- 데이터를 업데이트 하거나, 네트워크 요청을 하는 등의 효과들은 UI 랜더링과 별도로 유지하여 코드의 성능과 유지보수성을 향상시켜야 한다.

 JetPack Compose는 UI 랜더링 로직에서 사이드이펙트를 분리하고 별도의 코루틴 스코프에서 실행함으로써 개발자가 효과적으로 부수효과를 관리 할 수 있는 SideEffect, LaunchedEffect, DisposableEffect와 같은 여러 컴포저블 함수를 제공한다.

 

사이드 이펙트 사용의 주요 장점

- 향상된 성능 : 컴포저블 함수 이외의 non-UI 관련 작업을 실행함으로써 UI 랜더링 로직은 응답과 성능을 유지할 수 있다.

- 코드 가독성 향상 : UI 랜더링 로직에서 non-UI 관련 작업을 분리함으로서, 코드베이스를 이해하고 유지하기 쉬워진다.

- 더 나은 디버깅 : 로깅 및 분석 작업에 부수효과를 사용할 수 있으므로 개발자가 앱의 동작을 더 잘 이해하고 문제를 식별하는 데 도움이 될 수 있다.

 

SideEffect

- SideEffect은 컴포저블의 상위 컴포저블을 재구성할 때 부수효과를 실행할 수 잇는 컴포저블 함수이다. 보통 로깅, 분석, 외부 상태 업데이트 등 UI에 직접적인 영향을 주지 않는 작업을 수행한다. 이 함수는 컴포저블의 상태나 속성에 의존하지 않는 작업을 실행 할 때 유용하다.

 

SideEffect의 사용법

@Composable
fun HomeScreen(user: User) {
    val count by remember { mutableStateOf(0) }
       
    // composition이 성공할 때마다 호출
    SideEffect {
        analytics.setUserProperty("userType", user.userType)
    }
    
    Button(onClick = {count++}){
    	Text(text = "count 증가")
    }
    Text(text = "$count")
}

- recomposition 될 때 마다 실행 된다.

=> 버튼을 누를 때 마다, count가 증가되기에, 최하단의 텍스트뷰가 들어있는 HomeScreen 자체가 recomposition 된다.

LaunchedEffect의 사용법

@Composable
fun HOMEScreen() {
    Text(text = "text1")

    LaunchedEffect(key1 = Unit) {
        delay(2000)
        Toast.makeToast(context, "Toast MSG", Toast.LENGTH_SHORT).show()
    }
}

- composition 진입 및 key가 업데이트 될 때 실행된다.

- key가 변경될 때마다 다시 실행

- 코루틴 scope 지원

- 위의 예제에서는 key가 Unit이므로, Toast가 한 번만 표시된다.

 

@Composable
fun HomeScreen() {
    val list = remember { mutableStateListOf(1, 2, 3, 4, 5) }
    LaunchedEffect(key1 = list.size) {
        println("${list.size}")
        if (list.isEmpty()) {
            println("리스트가 비어있음")
        }
    }
    Button(onClick = { list.removeLast() }) {
        Text("삭제")
    }
}

 

 

- 1회성 이벤트를 호출할 때

- 상태 변경 시 non-composable 코드 호출

 

DisposableEffect의 사용법

@Composable
fun BroadcastReceiver(intentFilter: IntentFilter, onReceive: (Intent) -> Unit) {
    val context = LocalContext.current
    DisposableEffect(key1 = context) {
        val broadcastReceiver = object : BroadcastReceiver() {
            override fun onReceive(context: Context, intent: Intent) {
                onReceive(intent)
            }
        }
        context.registerReceiver(broadcastReceiver, intentFilter)
        onDispose {
            context.unregisterReceiver(broadcastReceiver)
        }
    }
}

- LaunchedEffect와 동일하게 동작하나, clean-up 기능이 있다.

- 코루틴 scope를 지원하지 않는다.

- onDispose

  • key가 변경되거나 Composablecomposition을 벗어나는 즉시 호출
  • 리소스 해제 관련 로직 수행

컴포저블 함수에서 코루틴 scope를 사용하는 방법

@Composable
fun ShowSnackbar() {
    Box(Modifier.fillMaxSize()) {
        val snackbarHostState = remember { SnackbarHostState() }
        val scope = rememberCoroutineScope()
        Button(onClick = {
            scope.launch {
                val result = snackbarHostState.showSnackbar(
                    "Message deleted", actionLabel = "Undo"
                )
                when (result) {
                    SnackbarResult.Dismissed -> {
                        // nothing to do
                    }
                    SnackbarResult.ActionPerformed -> {
                        // TODO perform undo
                    }
                }
            }
        }
        ) {
            Text("Delete")
        }
        SnackbarHost(
            hostState = snackbarHostState,
            modifier = Modifier.align(Alignment.BottomCenter),
        )
    }
}

- rememberCoroutineScope를 활용하여, 코루틴을 시작하는 데 사용할 수 있다.

- SideEffect Composable 또는 콜백 외부에서 코루틴을 실행하고 싶지 않을 경우에 사용한다.

- non-composable에서 코루틴을 시작하면, 정의되지 않은 순간에 코루틴을 시작할 위험이 존재한다.

 

참고

https://velog.io/@beokbeok/Compose-SideEffects%EC%9D%98-%EB%AA%A8%EB%93%A0-%EA%B2%83

 

Compose SideEffects에 대해 알아야할 모든 것

본 내용은 학습을 위해 Everything you need to know about Side Effects in Jetpack Compose with examples을 보고 입맛대로 정리한 글입니다.

velog.io

https://developer.android.com/jetpack/compose/side-effects?hl=ko

 

Compose의 부수 효과  |  Jetpack Compose  |  Android Developers

이 페이지는 Cloud Translation API를 통해 번역되었습니다. Compose의 부수 효과 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 부수 효과는 구성 가능한 함수의 범

developer.android.com

 

후기

사이드 이펙트를 사용하면서, 세 개의 차이점이 무엇인지 몰랐고, 대충 이름만 들었을 때에는 Launched니까 실행할 때, Dispose니까 끝날때인가? 이런생각을 하였는데, 찾아보니 사용할 곳이 많을 것 같았다.

예를 들어 현재 진행중인 회원가입 부분에서, 임의로 회원가입을 진행시켰다가 삭제하는데, 사용자가 중간에 나갔을 때, 어떻게 감지하고 삭제해야 하는가에 대한 생각을 하였으나 DisposableEffect를 사용해서 onDispose에서 처리하는 형태로 하면 어떨까 싶었다.

하지만 아마 강제종료 시엔 작동하지 않을 것 같아 걱정이다. 그렇게 된다면 아마, 라이프사이클을 감지해서 onStop()에서 처리를 하지 않을까 싶다.

반응형
LIST