Study Record

[Android] Compose 기초 - State, MutableState, remember 본문

안드로이드/compose

[Android] Compose 기초 - State, MutableState, remember

초코초코초코 2023. 8. 30. 18:13
728x90

😶 State (상태)

Compose 는 선언형 UI 프레임워크로 UI 모습을 코드를 선언 한다. 앱이 실행되는 동안 또는 앱이 사용자와 상호작용할 때 UI 를 변경하고자 하면, 예를 들어 사용자가 입력폼에 검색할 단어를 가상 키보드를 클릭한다면 내부 코드에서는 사용자의 키보드 입력 값을 받아 UI 를 다시 업데이트하여 입력 폼의 값을 업데이트해야 한다. 이 과정을 Compose 는 리컴포지션이라는 프로세스를 사용해 앱의 컴포지션을 업데이트하여 UI 를 업데이트할 것이다.

 

여기서 컴포지션은 Compose가 컴포저블을 실행할 때(컴포저블 함수에 표함된 UI 를 사용자에게 표시할 때) 빌드한 UI 에 대한 정보이다. 상태(State)가 변경되면 Compose 는 영향을 받는 컴포저블을 새 상태로 다시 실행하면서 리컴포지션이라는 업데이트된 UI 가 만들어진다. Compose 는 자동으로 리컴포지션을 예약할 수 있다.

 

리컴포지션은 Compose 가 데이터 변경사항에 따라 변경될 수 있는 컴포저블을 다시 실행한 다음 변경사항을 반영하도록 컴포지션을 업데이트한다.

 

컴포지션은 초기 컴포지션을 통해 생성되고 리컴퍼지션을 통해서만 업데이트된다. 컴포지션을 수정하는 것은 리컴포지션을 통해서이다. 이것을 가능하게 하려면 Compose 가 추적할 상태(State)를 알아야 한다. 그래야 Compose 가 업데이트 받을 때 리컴포지션을 예약할 수 있다.

 

😶 MutableState

Compose 는 State 혹은 MutableState 유형을 사용하여 앱의 상태를 Compose 에서 관찰 가능하거나 추적 가능한 상태로 설정할 수 있다. State 는 유형 값을 변경할 수 없고 읽는 것만 가능하다. MutableState 는 변경할 수 있다.

 

MutableState 유형을 반환하는 mutableStateOf() 함수를 사용할 수 있다. 이 함수에서 반환하는 값(Value)은 상태(State)를 보유하고 값 자체를 변경할 수 있다. 관찰 가능하므로 Compose 는 값의 변경을 관찰하고 리컴포지션을 트리거하여 UI 를 업데이트한다.

 

만약 다음과 같은 예시코드가 있다고 하면,

@Composable
fun EditTextField(modifier: Modifier = Modifier){
    var amountInput = mutableStateOf("0")

    TextField(
        value = amountInput.value,
        onValueChange = {
            amountInput.value = it
        },
        modifier = modifier
    )
}

사용자가 입력 폼에 값을 입력할 때마다 onValueChange 가 호출되어 amountInput 의 값이 변경될 것이다. amountInput 은 MutableState 유형으로 Compose 에 의해 상태(State)가 추적되어 값이 변경되면 즉시 리컴포지션이 예약되고,

EditNumberField() 컴포저블 함수가 다시 실행될 것이다. 하지만 amountInput 을 계속 mutableStateOf*("0") 즉, "0" 으로 초기화하고 있기 때문에 입력폼의 값은 사용자의 입력대로 변경되지 않고 계속 "0" 으로 보일 것이다.

 

이러한 문제를 해결하기 위해서는 리컴포지션 시 amountInput 변수의 값을 보존할 필요가 있다.

 

😶 remember 함수

컴포저블 함수안에서 remember 를 사용하여 리컴포지션에서 객체 자체를 저장할 수 있다. remember 함수로 계산된 값은 초기 컴포지션 중 컴포지션에 저장되고 저장된 값은 리컴포지션 중에 반환된다. 

 

상태와 업데이트가 UI 에 적절하게 반영되도록 일반적으로 컴포저블 함수에 remember 함수와 mutableStateOf 함수가 같이 사용된다.

import androidx.compose.runtime.remember
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue

@Composable
fun EditTextField(modifier: Modifier = Modifier){
    var amountInput by remember {
        mutableStateOf("0")
    }

    TextField(
        value = amountInput,
        onValueChange = {
            amountInput = it
        },
        modifier = modifier
    )
}

 

 

😶 상태 호이스팅

상태(State)를 가지고 있는지 아닌지에 따라 스테이트풀(Stateful), 스테이트리스(Stateless) 컴포저블로 나뉜다.

스테이트풀(Stateful) 컴포저블은 시간이 지남에 따라 변할 수 있는 상태(State)를 소유한 컴포저블이다.

스테이트리스(Stateless) 컴포저블은 상태(State)가 없는 컴포저블이다.

 

다음 예시에서 EditTextField 컴포저블은 amountInput 이라는 상태(State)를 가지고 있으므로 스테이트풀(Stateful) 컴포저블이다.

@Composable
fun EditTextField(modifier: Modifier = Modifier){
    var amountInput by remember {
        mutableStateOf("0")
    }

    TextField(
        value = amountInput,
        onValueChange = {
            amountInput = it
        },
        modifier = modifier
    )
}

 

 

 

앱에서 재사용할 수 있는 컴포저블을 만드는 경우 스테이트리스(Stateless) 컴포저블로 만들어야 한다. 또한 스테이트풀(Stateful) 컴포저블 내의 상태(State)에 접근해야 하는 경우 상태(State)를 추출하여 상위 요소로 옮겨 스테이트리스(Stateless) 컴포저블로 만들어야 하는데 이러한 과정을 상태 호이스팅이라고 한다.

 

 

즉, 구성요소를 스테이트리스(Stateless)로 만들기 위해 상태(State)를 호출자로 이동하는 패턴을 상태 호이스팅이라고 한다.

 

예를 들어, 다음과 같이 EditTextField 가 스테이트풀 컴포저블이고 Text() 의 텍스트 값을 EditTextField 컴포저블 내의 amountInput 상태 값을 사용해야 한다고 하자.

@Composable
fun EditTextLayout() {
    Column(
        modifier = Modifier.padding(40.dp).fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        EditTextField()
        Text("텍스트")
    }
}

@Composable
fun EditTextField(modifier: Modifier = Modifier){
    var amountInput by remember {
        mutableStateOf("0")
    }

    TextField(
        value = amountInput,
        onValueChange = {
            amountInput = it
        },
        modifier = modifier
    )
}

 

 

amountInput 이라는 상태(State)를 EditTextField 에서 EditTextLayout 컴포저블로 이동시키고 EditTextField 를 스테이트리스 컴포저블로 만든다. (상태 호이스팅)

@Composable
fun EditTextLayout() {
    var amountInput by remember {
        mutableStateOf("0")
    }

    Column(
        modifier = Modifier
            .padding(40.dp)
            .fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        EditTextField(
            value = amountInput,
            onValueChange = { amountInput = it }
        )
        Text(amountInput)
    }
}

@Composable
fun EditTextField(
    value: String,
    onValueChange: (String) -> Unit,
    modifier: Modifier = Modifier
){
    TextField(
        value = value,
        onValueChange = onValueChange,
        modifier = modifier
    )
}

 

 

😶 컴포저블의 수명 주기

앱의 UI 는 처음에 컴포지션이라는 프로세스에서 구성 가능한 함수를 실행하여 빌드된다. 상태(State)가 변경되면 리컴포지션이 예약되는데 리컴포지션은 상태가 변경되었을 수 있는 구성 가능한 하수를 Compose 에서 다시 실행하고 업데이트된 UI를 만드는 것을 의미한다. 

 

컴포지션을 만들거나 업데이트하는 유일한 방법은 초기 컴포지션 및 후속 리컴포지션을 통해서이다. 

 

구성 가능한 함수에는 Activity 수명 주기와 별대인 자체 수명주기가 있다. 수명 주기는 최초 컴포지션을 시작하고 0회 이상 재구성한 다음 컴포지션을 종료하는 이벤트로 구성된다.

 

Compose 가 리컴포지션을 추적하고 트리거하기 위해선 상태(State)가 변경된 시점을 알아야 한다. 변경된 시점을 알려면, 즉 객체의 상태를 추적함을 나타내기 위해 객체의 유형은 State 또는 MutableState 여야 한다.

 

아래의 예시에서 상태(Ex. revenue) 값이 변경되어 리컴포지션과정에서 구성 가능한 함수(Ex. TestItem())가 다시 실행될 때 revenue 는 계속해서 0으로 초기화된다.

@Composable
fun TestItem(){ 
    var revenue = mutableStateOf(0)

    Column() {
    /* */
    }
}

 

따라서, 리컴포지션 중에 값을 유지하고 재사용하도록 하려면 remember 을 사용해야 한다.

@Composable
fun TestItem(){ 
    var revenue by remember { mutableStateOf(0) }

    Column() {
    /* */
    }
}

 

remember API 를 사용하면 Compose 는 리컴포지션 중 상태(Ex. revenue)를 기억하지만 구성 변경 중에는 이 상태를 유지하지 않는다. Compose 가 구성 변경 중에 상태를 유지하려면 rememberSaveable 을 사용해야 한다.

 

rememberSabeable

"구성 변경 중" 의 의미는 종료한 앱이 다시 시작되는 것이라고 설명할 수 있다. Android 시스템은 한정된 자원을 가지고 있기 때문에 현재 사용자가 사용하지 않은 프로그램(앱)을 강제로 종료하거나 시각적 지연의 위험이 있을 때 강제로 종료하는 등 Android 시스템이 앱을 강제로 종료하는 경우가 있는데 사용자가 다시 해당 앱을 실행하면 다시 시작한다. 이렇게 다시 시작하는 과정을 "구성 변경 중" 의 사례 중 하나이다.

 

또한, 다크 모드에서 라이트 모드로 전환할때도 "구성 변경" 된다. 이러한 여러가지 구성 변경 상황에서 데이터의 손실을 막으려면 remember API 대신 rememberSaveable 를 사용해야 한다.

@Composable
fun TestItem(){ 
    var revenue by rememberSaveable { mutableStateOf(0) }

    Column() {
    /* */
    }
}

 

728x90

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

[Android] Compose 테마  (0) 2023.09.04
[Android] 계측 테스트 - Compose  (0) 2023.08.31
[Android] Compose 기초  (0) 2023.08.29
[Android] Compose modifier(수정자)  (0) 2023.08.29
[Android] Compose 컴포저블 참고사항  (0) 2023.08.28