CS/Android

State(MutableState)

졔졔311 2025. 2. 3. 16:44
728x90
반응형
  • state란?
    • application에서 시간에 따라 변할 수 있는 모든 값
      • ex) 스낵바, ripple effect 등등
    • 특정 UI 요소의 상태를 나타내며, 상태가 변경되면 해당 UI가 자동으로 다시 그려지도록 함(state update)
      • 변경된 상태를 UI에 반영하기 위한 추가적인 코드 필요 x → 간결
      • 데이터 상태와 UI 코드 분리 → 유지보수 쉬움
    • 선언형 UI(jetpack compose)에서 state를 업데이트하기 위해서는 동일한 composable을 새 인수로 호출해야 함.
      → state 업데이트마다 recomposition
    • recomposition이란?
      • state의 변경 시 영향을 받는 일부 UI만 업데이트하는게 효율적. 따라서 해당 composable 함수만 다시 실행. → remember 이용

 

  • remember
    • remember API : 객체를 메모리에 저장하는 기능 → 성능 최적화를 위함.
    • initial composition(초기 빌드한 UI)에서 실행될 때 계산된 값을 기억했다가 recomposition 시에 반환
      → 즉, recomposition 시에 다시 호출x
    • composable이 composition에서 제거 시 객체를 메모리에서 지움
      • 예시1)
        val a = remember { mutableStateOf(0) }
        -> 이런 식으로 단순히 remember를 통해 state를 지정하는 방식
      • 예시2)
        composable function 내부
        return remember(context){
          ... context를 이용한 내용
        }
        -> 이런 방식으로 context라는 매개변수(키) 기반 재구성하는 방식
    • rememberSaveable
      • ex) val remSaveState = rememberSaveable { mutableIntStateOf(0) }
      • remember는 state를 recomposition 시에는 유지하지만, 프로세스 종료나 구성 변경(화면 회전 등)이 발생하면 상태 유지x
        → rememberSaveable 사용하여 해결
      • Android의 저장 메커니즘(Bundle)을 활용해 데이터 저장 및 복원
        • 저장 가능한 데이터 유형 : Parcelable, Serializable, SavedStateRegistry
        • 이외의 데이터는 Saver를 이용해 저장 및 복원 방법을 정의해야 함
          • 예시
@Composable
fun CustomObjectExample() {
    val customState = rememberSaveable(stateSaver = MyCustomSaver) {    // stateSaver를 정의한 방식으로 사용 설정
        mutableStateOf(CustomData("Default", 1))                        // 원래는 저장 불가능한 데이터 형식
    }
    Button(onClick = { customState.value = CustomData("Updated", 2) }) {
        Text("Data: ${customState.value}")
    }
}
 
// CustomData 정의
data class CustomData(val name: String, val id: Int)
 
// Saver 정의
val MyCustomSaver = Saver<CustomData, Map<String, Any>>(
    save = { mapOf("name" to it.name, "id" to it.id) },
    restore = { CustomData(it["name"] as String, it["id"] as Int) }
)

 

  • MutableState
    • mutable vs immutable
      • mutable
        • 값이나 상태를 변경 가능한 객체
        • 데이터 변경 시 새로운 객체를 생성x
        • 다중 스레드 환경에서 관리 어려움(동기화 필요)
        • list를 예로 들면,
          var mutableList = mutableListOf(1,2,3)으로 선언한 경우,
          mutableList.add(4)를 하면 mutableList가 직접 변경됨
      • immutable
        • 값이나 상태를 변경 불가능한 객체
        • 한 번 생성되면 내부 상태 변경 불가
        • 데이터 변경 시 항상 새로운 객체를 생성함
        • 다중 스레드 환경에서 안전함
        • list를 예로 들면,
          val immutableList = listOf(1,2,3)으로 선언한 경우,
          immutableList.add(4) → 컴파일 에러가 발생.
    • state의 변경을 composable에 알리고, 함수를 recomposition하는 역할 → MutableStateOf를 사용
      • mutableStateOf : 값이 변경될 때 UI 재구성을 위해 사용. mutable이므로 값 변경 가능.
      • 변경되지 않는 값 : val로 선언하거나 고정된 값으로 관리 ex) 고정된 텍스트, 고정된 색상 값, 상수 데이터 등 
        • 참고 > MutableStateFlow와 MutableStateOf
            MutableStateFlow MutableStateOf
          공통점 앱에서 상태를 관리하기 위해 사용
          사용 목적 Flow API의 일부, StateFlow 확장 개념
          복잡한 앱 상태 관리

          composable 함수 간 상태 공유
          간단한 로컬 상태 관리
          특정 composable에서만 사용
          즉시 UI 업데이트
          사용 위치 Compose 외부 환경(ViewModel) 가능
          Coroutine 환경
          Composable 함수 내(UI layer)
          생명 주기 UI layer와 분리되어
          ViewModel의 생명 주기에 따라 관리
          Composable 함수의 생명 주기와
          함께 자동으로 관리
          기능 비동기 데이터 관리 해당 composable 내에서
          상태 변화를 쉽게 추적, 관리
    • MutableStateOf로 생성된 MutableState 객체는 Observable State(관찰 가능한 상태)를 제공
      이 state를 통해 composable 함수 recomposition 가능
      • observable state란?
        • 특정 값이 변경될 때 (Compose가) 이를 감지하고 자동으로 UI를 업데이트하도록 설계된 상태
        • 내부 동작
          1. MutableState 객체 내의 value 변경
          2. 이 객체가 composable 함수에 변화 알림
          3. composable 함수가 recomposition을 위한 요청을 compose 런타임에 전송(Compose가 state 변경 감지)
          4. compose가 변경이 필요한 UI 부분만 업데이트(UI Recomposition)
    • MutableState 객체 선언 방법
      • val mutableState = remember { mutableStateOf(default) }
        • mutableState는 mutableStateOf 객체를 직접 가리킴
        • state 객체를 직접 사용 가능 → state 관련 작업을 명시적으로 제어할 수 있음
          • ex)
            mutableState.value++
            println(mutableState.value)
            mutbaleState.value = 0  // 이렇게 초기화 가능
        • var value by remember { mutableStateOf(default) }
          • by 키워드를 사용하여 위임(Delegation) 활용
          • mutableStateOf()를 통해 생성된 state 객체의 값을 직접 다룰 수 있음 → 코드 간결
            • ex)
              mutableState++
              println(mutableState)
              // mutableState를 초기화하거나 이 state 객체를 다른 함수에 전달 불가
        • val (value, setValue) = remember { mutableStateOf(default) }
          • state 객체의 value와 setValue 구조 분해
            value → state 값을 읽는 역할 (mutableStateOf.value)
            setValue → state 값을 설정(update)하는 역할 (mutableStateOf.value = newValue)
          • 함수형 스타일에 가까워 복잡한 상태에서 더 유연하게 state 객체를 다룰 수 있음
            • ex)
              setValue(value+1)
              Text("Count: $count")
      • mutableStateOf()를 사용한 예시 - counter
        • 호출자(parent composable)가 state를 염두에 두지 않는 경우 유용
        • 내부 state를 갖기 때문에 재사용성x, 테스트 어려움
// remember를 사용해 객체를 저장하는 composable
@Composable
fun Counter() {
    var count by remember { mutableStateOf(0) } // <= 내부에서 count 관리
 
    Column {
        Text(text = "Count: $count")
        Button(onClick = { count++ }) {
            Text("Increment")
        }
    }
}

 

 

  • State Hoisting(상태 위임)
      • State를 composable 외부로 올려서 더 큰 범위에서 관리하는 것
      • composable 자체는 state 관리x, parent가 제어.
        → 재사용성 증가, 테스트 용이
      • UI 로직과 data 로직을 분리
      • state를 내부에 보관하지 않고, 호출자에게 전달받아 그걸 기반으로 UI 렌더링
      • state hoisting을 적용한 예시 - counter
        • 호출자가 state를 제어해야 하는 경우 유용
        • 예시
          // state를 갖지 않는 composable -> UI 로직
          @Composable
          fun Counter(
              count: Int,
              onIncrement: () -> Unit
          ) {
              // 호출자에게 전달받은 state를 기반으로 UI 렌더링
              Column {
                  Text(text = "Count: $count")
                  Button(onClick = onIncrement) {
                      Text("Increment")
                  }
              }
          }
           
          // State Hoisting 적용 ->  data 로직
          @Composable
          fun CounterScreen() {
              var count by remember { mutableStateOf(0) }
           
              // 호출자 CounterScreen()에서 Counter()를 호출하며 state를 넘겨줌 => 다른 composable 생성 시에도 state 공유 가능
              Counter(
                  count = count,
                  onIncrement = { count++ }
              )
          }
      • plain state holder(일반 상태 홀더 클래스) 
        • import androidx.compose.foundation.rememberScrollState
          ...
          val scrollable = rememberScrollState()
        • 위와 같은 방식으로 사용하는데,
          Compose 라이브러리에서 제공하는 것을 import.
          scrollable은 기본값 rememberScrollState()로 정의됨
        • 일반 상태 홀더 클래스인 rememberScrollState를 사용해 사용자가 스크롤할 때 스크롤 위치 및 상태를 추적
          • value 프로퍼티 : 현재 스크롤 위치 나타냄
          • maxValue 프로퍼티 : 스크롤 가능한 최대 위치 나타냄
          • remember를 통해 ScrollState 객체를 재사용하고, value와 maxValue를 관리하여 상태 추적 및 동작 제어가 쉬움
          • Column(...){
            ...
            TopTab(
                ...
                modifier = Modifier.horizontalScroll(scrollable),
            }
          • 이런 방식으로 Modifier를 사용해 만든 수평 스크롤 동작과, scroll state를 연결하기 위해 scrollable을 넘겨줄 수 있음
            스크롤 동작이 발생하면 scrollable.value가 업데이트 되는 방식

 

출처 :

Android Compose Tutorial  |  Jetpack Compose  |  Android Developers

헷갈려서 정리하는 StateFlow, state(상태), remember, MutableState

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

728x90
반응형

'CS > Android' 카테고리의 다른 글

Android Studio build 방법 - Make Project (Ctrl+F9)와 Build APK(s)  (0) 2025.02.25
Retrofit  (1) 2025.02.16
Flashing과 Fastboot  (1) 2025.01.06
SDK와 PDK  (1) 2025.01.06
Build Variant  (1) 2024.12.26