
Jetpack Compose 의 Composable 은 기본적으로 State-less 입니다. State 는 Screen-Level 에서 Composable 로 전파되고, Composable 에서 발생하는 Event 는 Screen-Level 로 전파됩니다. 이와 같이 구현된 이유는 재사용성 및 테스트 가능성 때문입니다.
관련된 내용은 이전에 상태 호이스팅 관련 포스팅에 설명해두었습니다.
[Jetpack Compose] 상태 호이스팅
기존XML 방식에는 큰 문제점이 하나 있었는데, 그것은 바로 View 가 스스로의 상태를 정의하고 보존한다는 것입니다. 상태는 View 를 그 자체로 인식할 수 없도록 하며, 이는 곧 테스트 가능성과 재
blothhundr.tistory.com
Composable 내에서 참조하는 State 의 값이 변경되는 경우, 해당 값에 따라 UI 업데이트가 이루어져야 하기 때문에, Composable 에 Recomposition 이 발생하게 됩니다. 이를 통해 해당 Composable 이 갖고 있던 값은 지워지고, 추적하던 State 와 동일한 State 에 갱신된 새로운 값을 참조하여 화면에 표시합니다.
다만, State 가 Composable 외부에 있고 이를 Composable 이 파라미터로 받는 경우에만 해당하는 이야기입니다. State 가 딱히 Composable 외부에 있어야 할 이유가 없는 경우에는 코드 가독성을 위해 Composable 내부에 State 를 작성하고 싶을 수 있는데, 이 때에는 문제가 발생할 수 있습니다.
Composable 과의 상호작용을 통해 State 에 홀드된 값이 변화되면 자연스럽게 해당 Composable 은 Recomposition 에 돌입합니다. Recomposition 중에는 State 에 대한 참조가 해제되므로, 코드 베이스에 작성해둔 초기값만을 가질 수 있습니다.
이를 위해 고안된 것이 remember 입니다.
<T> remember()
val buttonClickedState = remember { mutableStateOf(false) }
var buttonClickedState by remember { false }
보통 위와 같이 사용합니다.
전자는 val 로 선언되어 있기 때문에, 해당 객체의 value 프로퍼티로 접근 및 재할당이 가능합니다.
후자는 var 로 선언되어 있기 때문에 직접 접근하고 직접 재할당할 수 있습니다. getter, setter 는 by 키워드를 통한 Delegation 으로 remember 에 의해 처리됩니다.
val (buttonClickedState, setButtonClickedState) = remember { mutableStateOf(false) }
setButtonClickedState(true)
이런 방법도 있는데, getter 는 변수의 value 프로퍼티로 접근하고 값 할당은 함수로 수행하는 방법입니다.
🤔 세 가지 방법에는 어떤 차이가 있나요?
별 차이 없습니다. 안드로이드 공식 문서에서도 셋은 같다고 말합니다. 코드 구성에 따라 세 가지 방법 중 기존 코드와 융화되었을 때 가독성이 더 좋은 쪽을 선택하면 됩니다.

🤔 다른 remember 도 있던데...
remember() with key
파라미터로 key 를 받는데, key 로 할당된 변수의 값이 변경될 때 UI 를 업데이트하는 로직을 갖습니다.
글쎄요, 사양의 변화가 없다면 저는 자주 사용하진 않을 것 같습니다.
rememberSaveable
내부적으로 Bundle 을 사용하도록 구현되어 있습니다. 설정 변경시에도 값이 유지되어야 하는 경우에 사용합니다. 가로/세로 모드 또는 라이트/다크 모드를 지원하는 앱이라면 사용해봄직 합니다.
remember 는 어떻게 작동하는가

remember 에 대한 설명입니다.
calculation 함수를 통해 제공되는 값을 기억하도록 합니다. calculation 함수는 Composition 에 의존하여 수행됩니다.
Recomposition 은 Composition 을 통해 계산된 값을 반환합니다.
설명이 조금 모호하니 코드를 봅니다.
Composer
Compose Kotlin Compiler Plug-In 이 대상으로 하고, Code Generation Helper 가 사용하는 인터페이스 입니다. 설명이 복잡하게 돼 있는데, 추상화로 인한 것이니 우리는 'Composable 의 Composition 을 위해 가장 필요한 존재' 정도로 생각하면 될 것 같습니다.
Cache
Composer 의 확장함수이며, 말 그대로 캐시 기능입니다. Composition 내부의 데이터를 기억할 수 있도록 합니다. 이는 remember 를 위해 사용됩니다.
@ComposeCompilerApi
inline fun <T> Composer.cache(invalid: Boolean, block: @DisallowComposableCalls () -> T): T {
@Suppress("UNCHECKED_CAST")
return rememberedValue().let {
if (invalid || it === Composer.Empty) {
val value = block()
updateRememberedValue(value)
value
} else it
} as T
}
코드 자체는 굉장히 간단합니다.
invalid 는 해당 Composable 의 활성화 여부를 의미합니다. Composable 이 활성화 상태이거나, 기존 저장된 값이 있지는 않은지 확인하고, 없는 경우 새로운 값을 할당합니다. 둘 모두에 해당하지 않는 경우 그대로 반환되나, Composable 이 활성화되지 않은 상태이기 때문에 GC 에 의해 메모리가 해제됩니다.
Composer 내부에 구현된 Cache 를 통해 값을 기억하게 한다.
정도로 정리할 수 있습니다.
'Android > Tech' 카테고리의 다른 글
안드로이드와 Clean Architecture (0) | 2023.02.11 |
---|---|
JVM 의 Garbage Collector (0) | 2023.02.01 |
Serializable, Parcelable (0) | 2023.01.12 |
SharedPreferences 가 DataStore 에 대체될 수 밖에 없었던 이유 (0) | 2022.12.27 |
비트맵과 WebP (0) | 2022.12.25 |