[Android][Compose] Composable과 Recomposition

 

컴포즈의 사용 이유와 선언형 UI에 대해 알아보았다면, 이제 코드랩을 열어보자. 벌써부터 처음보는게 생긴다. 바로 컴포저블 ㅎ ㅎ .. ^^ 오늘은 컴포저블이 무엇인지 어떻게 쓰이는지 상태 관리는 어떻게 하는지에 대해 공부해보도록 하자. 

 

 

용어 정리

  • 컴포저블 함수
    • 데이터를 수신할 수 있고, UI를 만들기 위해 수신받은 데이터를 사용하며, 사용자가 화면에서 볼 수 있는 UI 구성요소를 내보낸다.
  • 컴포지션
    • Jetpack Compose가 컴포저블을 실행할 때 빌드한 UI
    • UI를 기술하는 컴포저블의 트리 구조
  • 리컴포지션
    • 데이터가 변경될 때 컴포지션을 업데이트하기 위해 컴포저블을 다시 실행하는 것

 

 

컴포저블의 생명 주기 

 

컴포즈는 초기 컴포지션 시 처음으로 컴포저블을 실행할 때 컴포지션에서 UI를 표현하기 위해서 해당 컴포저블을 계속해서 추적한다. 그리고 앱의 상태가 바뀌면, 컴포즈는 리컴포지션을 스케줄한다.

 

따라서 요약하면 컴포저블의 생명주기는 다음과 같다.

  • 컴포지션 진입
  • 리컴포지션
  • 컴포지션 종료 

 

 

컴포지션은 초기 컴포지션으로만 생성되고 리컴포지션으로만 업데이트된다. 컴포지션을 바꾸는 유일한 방법은 리컴포지션임을 기억하자. 

 

또 컴포저블에 대해 다음을 기억하자.

  • 컴포저블 함수 순서와 관계없이 실행할 수 있다
  • 컴포저블 함수 동시에 실행할 수 있다
  • 컴포저블 함수는 애니메이션의 모든 프레임에서와 같은 빈도로 매우 자주 실행될 수 있다

 

 

리컴포지션

 

그렇다면 리컴포지션은 무엇일까 ? 먼저 배경에 대해 알아보자. 이전 포스트에서 말했듯이 컴포즈는 기존xml 뷰와 다르게 앱이 화면을 그리면 변경할 수 없다. 그러면 필요 시 어떻게 화면을 업데이트하냐고 ? 바로 컴포저블의 상태를 변경함으로서 구현한다. 

 

먼저 예제를 살펴보자. 

 

@Composable
private fun HelloContent() {
    Column(modifier = Modifier.padding(16.dp)) {
        Text(
            text = "Hello!",
            modifier = Modifier.padding(bottom = 8.dp),
            style = MaterialTheme.typography.bodyMedium
        )
        OutlinedTextField(
            value = "",
            onValueChange = { },
            label = { Text("Name") }
        )
    }
}

 

위 코드를 실행했을 때 OutlinedTextField(xml 기반에선 editText라고 생각하면 된다)에 텍스트를 입력하면 의도처럼 텍스트의 값이 변하지 않을 것이다. 이는 TextField가 자체적으로 업데이트되지 않고 value 매개변수가 변경될 때 업데이트되기 때문이다.

 

 

리컴포지션이란 데이터가 변경될 때 컴포지션을 업데이트 하기 위해 컴포저블을 다시 실행하는 것으로, 컴포저블의 입력값이 변경될 때만 실행된다. 이전 포스트에서 '컴포즈는 특정 시점에 UI의 어떤 부분을 다시 그려야 하는지를 지능적으로 선택할 수 있다' 라고 언급한 적이 있는데, 그게 바로 리컴포지션이다. 컴포즈는 변경된 컴포저블만 똑똑하게 리컴포지션할 수 있고, 이게 바로 전체 UI 트리를 재구성했던 기존의 명령형 UI (xml 기반 뷰)에 비해 효율적이라고 불리우는 이유이다. 

 

리컴포지션은 일반적으로 State<T> 객체가 변경되면 트리거된다. Compose는 이러한 객체를 추적하고 컴포지션에서 특정 State<T>를 읽는 모든 컴포저블 및 호출하는 컴포저블 중 건너뛸 수 없는 모든 컴포저블을 실행한다.

 

리컴포지션에 대해 다음을 기억하자.

  • 리컴포지션은 최대한 많은 수의 구성 가능한 함수 및 람다를 건너뛴다
  • 리컴포지션은 낙관적이며 취소될 수 있다

 

 

remeber

리컴포지션까지 이해하고 나면 이런 궁금증이 들 수 있다.

 

만약 리컴포지션이 끝나기전에 동일한 컴포저블에 대하여 다른 리컴포지션이 필요한 경우 어떻게 될까 ?

아주 좋은 생각이다. 이 경우에 컴포저블은 리컴포지션을 취소하고 새 상태로 리컴포지션을 다시 시작한다.

 

또, 리컴포지션 시 이전의 데이터는 따로 저장해두지 않아도 유지가 될까 ? 아니다. 이게 앞으로 이야기할 remember의 등장배경이다. 

 

컴포저블 함수는 remember API를 사용해 메모리에 객체를 저장할 수 있다. 이 remember에 의해 계산된 값은 컴포지션 중에 컴포지션에 저장되고, 저장된 값은 리컴포지션 중에 반환된다. remember는 변경 가능한 객체뿐만 아니라 변경할 수 없는 객체를 저장하는 데 사용할 수 있다. 그리고 이 remember를 호출한 컴포저블이 컴포지션에서 삭제되면 저장한 객체 또한 삭제한다. 

 

런타임 시 컴포즈에서는 관찰 가능한 MutableState<T>를 생성하는 mutableStateOf를 관찰 가능하고, 컴포저블에서는 이 MutableState 객체를 관찰한다. 그리고 컴포저블에서 이 객체를 선언하는데는 세가지 방법이 있다.

  • val mutableState = remember { mutableStateOf(default) }
  • var value by remember { mutableStateOf(default) }
  • val (value, setValue) = remember { mutableStateOf(default) }

또 화면 회전과 같이 구성 변경에까지 대응하는 데이터 저장에는 rememberSaveable 을 사용한다. 

 

 

이렇게 remember와 mutableState를 활용하면 리컴포지션 시 데이터를 기억할 수 있다. 위의 예제는 이를 적용해 다음과 같이 변경하면 정상적으로 동작한다. 

 

@Composable
fun HelloContent() {
    Column(modifier = Modifier.padding(16.dp)) {
        var name by remember { mutableStateOf("") }
        if (name.isNotEmpty()) {
            Text(
                text = "Hello, $name!",
                modifier = Modifier.padding(bottom = 8.dp),
                style = MaterialTheme.typography.bodyMedium
            )
        }
        OutlinedTextField(
            value = name,
            onValueChange = { name = it },
            label = { Text("Name") }
        )
    }
}

 

 

 

이렇게 이번 포스트에서는 컴포저블과 컴포지션을 알아보면서 생명주기와 리컴포지션과 remember까지 공부해보았다. 이어서는 컴포저블의 상태에 대해서 알아보자. 

 

 

 

 

 

참고자료

 

Compose 이해  |  Jetpack Compose  |  Android Developers

이 페이지는 Cloud Translation API를 통해 번역되었습니다. Compose 이해 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. Jetpack Compose는 Android를 위한 현대적인 선언

developer.android.com

 

 

상태 및 Jetpack Compose  |  Android Developers

이 페이지는 Cloud Translation API를 통해 번역되었습니다. 상태 및 Jetpack Compose 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 앱의 상태는 시간이 지남에 따라

developer.android.com