성장기록지

State hoisting 탐구 (재사용성, plain state holder class) 본문

안드로이드/안드로이드 지식

State hoisting 탐구 (재사용성, plain state holder class)

pengcon 2025. 1. 10. 13:51

컴포저블 내부에서 호이스팅

그림과 같은 예시를 보자.

전체 화면을 ConverSationScreen이라고 하고, 

메시지가 보이는 목록들이 MessagesList,

Jump to Bottom이라고 작성되어있는 버튼을 Button,

Hi there! 이라고 적혀있는 입력창은 userInput의미한다.

 

그렇다면 요구사항은 다음과 같을 것이다.

"Jump to bottom을 누르면 MessagesList 가장 아래로 가게 해줘"

"새로운 메시지를 send해도 가장 아래로 가게 해줘"

 

그렇다면 계층구조는 어떻게 되어있을까? 

그림과 같이 연결되어 있을 것이다.

 

여기서 우리는 State hoisting을 이용해 재사용성을 높이고 싶다는 생각이 들 것이다.

그렇다면 어느 State를 어디까지 호이스팅 해야할까?

 

공식문서에서 아래와 같이 친절하게 GIF 로 설명이 되어있다.

LazyColumn의 스크롤을 조작해야 하기 때문에, UserInput과 Button 모두 LazyListState가 필요할 것이다.

그렇다면 이 모든 컴포저블 함수의 가장 가까운 공통 조상(The lowest common ancestor)

까지 hoisting을 하면 될 것이다!

 

요약하자면 아래의 그림과 같이 될 것이다.

이렇게 UI 요구 사항에 따라 Lowest common ancestor까지 state hoisting을 하는 건

꽤나 자주 있는 일이므로 가까운 공통 조상을 찾는것을 명심하면 좋을것이다!

 

아래는 설명에 대한 전체 코드이다.

@Composable
private fun ConversationScreen(/*...*/) {
    val scope = rememberCoroutineScope()

    val lazyListState = rememberLazyListState() // State hoisted to the ConversationScreen

    MessagesList(messages, lazyListState) // Reuse same state in MessageList

    UserInput(
        onMessageSent = { // Apply UI logic to lazyListState
            scope.launch {
                lazyListState.scrollToItem(0)
            }
        },
    )
}

@Composable
private fun MessagesList(
    messages: List<Message>,
    lazyListState: LazyListState = rememberLazyListState() // LazyListState has a default value
) {

    LazyColumn(
        state = lazyListState // Pass hoisted state to LazyColumn
    ) {
        items(messages, key = { message -> message.id }) { item ->
            Message(/*...*/)
        }
    }

    val scope = rememberCoroutineScope()

    JumpToBottom(onClicked = {
        scope.launch {
            lazyListState.scrollToItem(0) // UI logic being applied to lazyListState
        }
    })
}
 
글을 다 읽고 나서, 열정적인 여러분이라면 LazyListState에 대해 궁금 할 것이다.
어떤 State이길래 rememberLazyState()로 remember와 State가 합친 함수까지 제공하는 것일까 생각하실 수 있다. 

아래는 LazyListState와 같은 plain state holder class를 설명해보겠다.

 

Plain state holder class를 State Owner로 사용하기

공식문서에서는, "컴포저블 함수에서 복잡한 UI 로직을 다룰 때 그 로직을 plain state holder class와 같은

상태 홀더 클래스에 위임하라" 고 되어있다.

 

이렇게 하면 테스트 용이, 복잡성 감소, 책임 분리 원칙 준수와 같은 효과가 있고,

컴포저블 함수 호출자에게 편리한 함수를 제공하여, 호출자가 이 로직을 직접 작성할 필요가 없게 만든다고 한다.

 

plain state holder class의 예시는 방금 언급한 LazyListState가 있고,

rememberLazyState()와 같은 편리한 함수를 제공한다. (컴포저블 생명주기를 따르므로 가능하다.)

(참고, rememberLazyState는 remember가 아닌 rememberSavable을 사용한다.)

 

그렇다면 LazyListState는 어떠한 복잡한 UI 로직을 다루는 걸까? 코드를 살펴보자.

LazyListState는 이 UI 요소의 scrollPosition을 저장하는 LazyColumn의 상태를 캡슐화한다.

또한 특정 항목으로 스크롤하는 등의 방식으로 스크롤 위치를 수정하는 메서드도 노출한다.

이렇게 컴포저블의 책임을 늘리면 상태 홀더의 필요성이 증가하게 된다!

 

그렇다면 기존에 있는 State holder class만 사용하는가? 당연히 아니다.

필요에 따라서 프로젝트 참여자들과의 조율로 plain state holder class를 커스텀하면 된다!!

@Stable
class LazyListState constructor(
    firstVisibleItemIndex: Int = 0,
    firstVisibleItemScrollOffset: Int = 0
) : ScrollableState {
    /**
     *   The holder class for the current scroll position.
     */
    private val scrollPosition = LazyListScrollPosition(
        firstVisibleItemIndex, firstVisibleItemScrollOffset
    )

    suspend fun scrollToItem(/*...*/) { /*...*/ }

    override suspend fun scroll() { /*...*/ }

    suspend fun animateScrollToItem() { /*...*/ }
}

 

 

참고 자료

https://developer.android.com/develop/ui/compose/state-hoisting?hl=ko

 

상태를 호이스팅할 대상 위치  |  Jetpack Compose  |  Android Developers

이 페이지는 Cloud Translation API를 통해 번역되었습니다. 상태를 호이스팅할 대상 위치 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. Compose 애플리케이션에서

developer.android.com

https://nanamare.tistory.com/251

 

[번역] Crash Course on the Android UI Layer Part 2

해당 블로그의 글은 https://developer.android.com/topic/architecture/ui-layer 에 대한 정리하는 글 입니다. 우리는 UI Layer 와 관련된 많은 Entity 들을 살펴보고, 각각의 부분에 대해 이해하고, 모범 사례에 대해

nanamare.tistory.com

 

'안드로이드 > 안드로이드 지식' 카테고리의 다른 글

안드로이드 MVVM 아키텍쳐란?  (0) 2025.01.13
data binding이란?  (0) 2025.01.11
Compose State와 State hoisting  (0) 2025.01.08
Android의 Clean Architecture란?  (0) 2025.01.07
android 권장 아키텍쳐란?  (0) 2025.01.02