본문 바로가기

Android/Tech

Jetpack Compose 의 remember

Unsplash, Toby Stodart.

 

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 프로퍼티로 접근하고 값 할당은 함수로 수행하는 방법입니다.

🤔 세 가지 방법에는 어떤 차이가 있나요?

별 차이 없습니다. 안드로이드 공식 문서에서도 셋은 같다고 말합니다. 코드 구성에 따라 세 가지 방법 중 기존 코드와 융화되었을 때 가독성이 더 좋은 쪽을 선택하면 됩니다.

 

Android Developer.

 

🤔 다른 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