Slide 1

Slide 1 text

Jetpack Compose 생애 주기와 부수효과 1

Slide 2

Slide 2 text

생애 주기 개요 Jetpack Compose는 UI를 컴포지션(Composition) 으로 구성하고, 앱의 상태가 변경될 때 리컴포지션(Recomposition) 을 수행하여 UI를 업데이트합니다. 2

Slide 3

Slide 3 text

컴포지션의 흐름 1. 초기 컴포지션: UI가 처음 생성될 때 실행됨 2. 리컴포지션: 상태가 변경될 때, 필요한 UI 요소만 업데이트됨 3. 컴포지션 종료: 더 이상 필요 없는 UI 요소가 제거됨 @Composable fun MyComposable() { Column { Text("Hello") Text("World") } } 4

Slide 4

Slide 4 text

상태 변화와 리컴포지션 리컴포지션은 일반적으로 State 객체가 변경될 때 트리거됩니다. @Composable fun LoginScreen(showError: Boolean) { if (showError) { LoginError() } LoginInput() } 5

Slide 5

Slide 5 text

리스트에서의 리컴포지션 컴포저블이 리스트 내에서 여러 번 호출되면 각 호출은 고유한 인스턴스를 생성합니다. @Composable fun MoviesScreen(movies: List) { Column { for (movie in movies) { MovieOverview(movie) } } } 6

Slide 6

Slide 6 text

key() 를 활용한 최적화 리스트 내 컴포저블을 key() 로 구분하면 불필요한 리컴포지션을 방지할 수 있습니다. @Composable fun MoviesScreenWithKey(movies: List) { Column { for (movie in movies) { key(movie.id) { MovieOverview(movie) } } } } 7

Slide 7

Slide 7 text

Compose 렌더링 3 단계 (Phases) 1. 컴포지션(Composition): UI를 선언하고 구성 2. 레이아웃(Layout): UI 요소의 위치와 크기 결정 3. 그리기(Draw): 화면에 UI를 실제로 렌더링 9

Slide 8

Slide 8 text

1. 컴포지션 단계 (Composition) UI의 구조를 정의하는 단계 @Composable 함수가 실행되며, UI 트리가 생성됨 val padding by remember { mutableStateOf(8.dp) } Text( text = "Hello", // The `padding` state is read in the composition phase // when the modifier is constructed. // Changes in `padding` will invoke recomposition. modifier = Modifier.padding(padding) ) 10

Slide 9

Slide 9 text

2. 레이아웃 단계 (Layout) UI 요소의 크기와 위치를 결정 UI 트리를 바탕으로 레이아웃이 계산됨 var offsetX by remember { mutableStateOf(8.dp) } Text( text = "Hello", modifier = Modifier.offset { // The `offsetX` state is read in the placement step // of the layout phase when the offset is calculated. // Changes in `offsetX` restart the layout. IntOffset(offsetX.roundToPx(), 0) } ) 11

Slide 10

Slide 10 text

3. 그리기 단계 (Draw) 실제 화면에 UI를 렌더링하는 단계 Canvas 또는 Modifier.drawBehind 등을 사용하여 그래픽을 직접 그림 var color by remember { mutableStateOf(Color.Red) } Canvas(modifier = modifier) { // The `color` state is read in the drawing phase // when the canvas is rendered. // Changes in `color` restart the drawing. drawRect(color) } 12

Slide 11

Slide 11 text

부수 효과 (Side Effects) 컴포저블은 부수 효과(Side Effects) 없이 동작하는 것이 이상적이지만, API 요청이나 네트워크 호출이 필요한 경우 Effect API 를 활용해야 합니다. 14

Slide 12

Slide 12 text

LaunchedEffect LaunchedEffect는 특정 키 값이 변경될 때 한 번만 실행되는 정지 함수를 실행합니다. LaunchedEffect(pulseRateMs) { while (isActive) { delay(pulseRateMs) alpha.animateTo(0f) alpha.animateTo(1f) } } 15

Slide 13

Slide 13 text

DisposableEffect DisposableEffect는 컴포저블이 사라질 때 정리해야 하는 리소스가 있을 때 사용합니다. DisposableEffect(lifecycleOwner) { val observer = LifecycleEventObserver { _, event -> if (event == Lifecycle.Event.ON_START) { onStart() } else if (event == Lifecycle.Event.ON_STOP) { onStop() } } lifecycleOwner.lifecycle.addObserver(observer) onDispose { lifecycleOwner.lifecycle.removeObserver(observer) } } 16

Slide 14

Slide 14 text

SideEffect SideEffect는 Compose 상태를 Compose 외부의 코드와 동기화할 때 사용됩니다. Compose 내부의 상태가 변경될 때마다 실행됩니다. @Composable fun rememberFirebaseAnalytics(user: User): FirebaseAnalytics { val analytics: FirebaseAnalytics = remember { FirebaseAnalytics() } SideEffect { analytics.setUserProperty("userType", user.userType) } return analytics } 17

Slide 15

Slide 15 text

rememberCoroutineScope 컴포저블 외부에서 코루틴을 실행할 수 있는 범위를 제공하는 API입니다. @Composable fun MoviesScreen(snackbarHostState: SnackbarHostState) { val scope = rememberCoroutineScope() Scaffold( snackbarHost = { SnackbarHost(hostState = snackbarHostState) } ) { contentPadding -> Column(Modifier.padding(contentPadding)) { Button( onClick = { scope.launch { snackbarHostState.showSnackbar("Something happened!") } } ) { Text("Press me") } } } 18

Slide 16

Slide 16 text

rememberUpdatedState 값이 변경되더라도 다시 시작되지 않도록 값을 유지하는 데 사용됩니다. @Composable fun LandingScreen(onTimeout: () -> Unit) { val currentOnTimeout by rememberUpdatedState(onTimeout) LaunchedEffect(true) { delay(3000) currentOnTimeout() } } 19

Slide 17

Slide 17 text

produceState Compose 외부의 비동기 데이터를 Compose 상태로 변환하는 API입니다. @Composable fun loadNetworkImage( url: String, imageRepository: ImageRepository = ImageRepository() ): State> { return produceState>(initialValue = Result.Loading, url, imageR val image = imageRepository.load(url) value = if (image == null) Result.Error else Result.Success(image) } } 20

Slide 18

Slide 18 text

derivedStateOf 상태 객체를 다른 상태로 변환하는 데 사용됩니다. 자주 변경되는 상태를 기반으로 새로운 상태를 만들 때 최적화할 수 있습니다. @Composable fun MessageList(messages: List) { val listState = rememberLazyListState() LazyColumn(state = listState) { items(messages) { message -> MessageItem(message) } } val showButton by remember { derivedStateOf { listState.firstVisibleItemIndex > 0 } } AnimatedVisibility(visible = showButton) { ScrollToTopButton() } 21

Slide 19

Slide 19 text

최적화 전략 22

Slide 20

Slide 20 text

최적화 전략 1. ­ 리컴포지션 최소화 key()를 활용하여 리스트 성능 최적화 모든 상태를 최대한 낮은 단계에서 읽기 22

Slide 21

Slide 21 text

최적화 전략 1. ­ 리컴포지션 최소화 key()를 활용하여 리스트 성능 최적화 모든 상태를 최대한 낮은 단계에서 읽기 2. ­ 부수 효과 최소화 rememberCoroutineScope() 활용하여 필요할 때만 코루틴 실행 SideEffect를 외부 API 연동에만 사용 DisposableEffect를 사용해 리소스 정리 필수 22

Slide 22

Slide 22 text

Q & A ❓ 23

Slide 23

Slide 23 text

No content