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

이윤설·2024년 10월 20일
0

안드로이드 연구소

목록 보기
8/33

StateFlow

StateFlow는 애플리케이션에서 상태를 관리하고 표현하기 위해 Kotlin Flow 라이브러리의 일부입니다.

  • 읽기 전용 인터페이스입니다.
  • 항상 업데이트된 값을 반환합니다.
  • 구현된 Flow에서 값을 수집합니다.

인사이트:

StateFlow는 값이 업데이트된 경우에만 반환하며, 같은 값을 반복해서 반환하지 않습니다.

간단한 예:

두 개의 값 ab를 고려해 보겠습니다. 여기서 a는 처음 방출된 값이고, b는 방출할 값입니다.

  • if (a == b) { do nothing }
  • else if (a != b) { return b }

StateFlow를 RxJava의 Subject와 비슷하게 생각할 수 있습니다.

StateFlow에 대해 더 알아보기

  1. StateFlow는 핫 플로우입니다. 이는 활성 인스턴스가 수집자의 존재와 상관없이 메모리에 존재한다는 의미입니다.

    • 핫 플로우(hot flow)란, 데이터의 생산자가 수집자의 존재와 관계없이 데이터를 지속적으로 방출하는 흐름을 의미합니다. 즉, StateFlow의 인스턴스가 메모리에 존재하면, 다른 컴포넌트가 이를 관찰하지 않아도 값이 방출됩니다. 이러한 특성 덕분에 StateFlow는 UI 상태를 관리하는 데 매우 유용합니다. 예를 들어, 사용자가 앱을 탐색할 때 이전 화면에서의 상태를 기억하고 관리할 수 있습니다.
  2. 현재 값은 value 속성을 통해 검색할 수 있습니다.

    • StateFlow는 항상 현재 값을 가지고 있으며, 이 값은 value 속성을 통해 접근할 수 있습니다. 이는 값이 변할 때마다 즉시 확인할 수 있게 해주며, UI 업데이트나 다른 비즈니스 로직에서 필요한 데이터를 쉽게 가져올 수 있습니다. 예를 들어, myStateFlow.value를 호출하면 현재 저장된 값을 받을 수 있습니다. 이는 StateFlow의 특징 중 하나로, 상태를 쉽게 읽고 관리할 수 있도록 도와줍니다.
  3. Android에서 StateFlow는 관찰 가능한 변경 가능한 상태를 유지해야 하는 클래스에 적합합니다.

  4. StateFlow는 Android 애플리케이션에서 상태를 관리하고 관찰하기 위해 특별히 설계되었습니다.

  5. UI 관련 데이터를 관리하기 위한 LiveData의 대안으로 자주 사용됩니다.

  6. 상태에 대한 단일 진실의 출처를 유지하고, 모든 수집자가 최신 상태로 자동 업데이트되도록 할 때 StateFlow를 사용합니다.

    • StateFlow는 데이터의 상태를 관리하는 데 있어 단일 진실의 출처(single source of truth) 원칙을 따릅니다. 즉, 상태 데이터는 항상 하나의 출처에서 관리되며, 이를 통해 애플리케이션의 다양한 부분에서 동일한 상태 정보를 공유할 수 있습니다. 예를 들어, ViewModel에서 StateFlow를 사용하면 여러 UI 구성 요소가 동일한 상태 데이터를 참조하고, 하나의 값이 변경되면 모든 수집자가 자동으로 업데이트됩니다. 이로 인해 데이터 일관성이 보장되고, UI에서 데이터 동기화 문제가 줄어듭니다.

StateFlow 다이어그램

StateFlow 사용 이유

StateFlow는 감독 제어, 작업 스케줄링, 오류 관리, 통신 프로토콜, 사용자 인터페이스 및 하이브리드 시스템을 설계하고 개발할 수 있게 해줍니다. Android에서 StateFlow는 관찰 가능한 변경 가능한 상태를 유지해야 하는 클래스에 적합합니다.

StateFlow의 생명주기

StateFlow의 생명주기는 일반적으로 Activity나 Fragment와 같은 그것을 보유하는 구성 요소의 생명주기를 따릅니다. StateFlow의 주요 목적은 데이터의 반응형 스트림을 제공하는 것이며, 그 행동은 관찰하는 구성 요소의 생명주기에 밀접하게 연결되어 있습니다.

  1. 생성: StateFlow는 보통 ViewModel 또는 다른 데이터 관리 구성 요소 내에서 생성되고 초기화됩니다. 이는 관련 Android 구성 요소(예: Activity 또는 Fragment)가 생성될 때 발생합니다.
class MyViewModel : ViewModel() {
    // StateFlow 생성 및 초기화
    private val _userName = MutableStateFlow("Initial Name") // 내부적으로 사용하는 MutableStateFlow
    val userName: StateFlow<String> get() = _userName // 외부에서 관찰할 수 있는 StateFlow

    // 상태를 업데이트하는 함수
    fun updateUserName(newName: String) {
        _userName.value = newName // 상태 업데이트
    }
}
  1. 관찰: StateFlow를 관찰해야 하는 구성 요소는 보통 Android 구성 요소의 onCreate 또는 onCreateView 메서드에서 구독합니다. 여기에서 collect, onEach 또는 launchIn과 같은 함수를 사용하여 관찰자를 등록합니다.
    viewModel.myStateFlow.collect { state ->
        // 상태 변경 처리
    }
  2. 상태 업데이트: StateFlow는 사용자 상호작용이나 데이터 변경에 따라 필요할 때 업데이트됩니다. mutable StateFlow의 경우 value 속성을 사용하여 업데이트할 수 있습니다.
    viewModel.updateState("New Value")
  3. 관찰 계속: 관찰자는 활성 상태이고 StateFlow가 여전히 범위에 있는 한 계속해서 업데이트를 수신합니다.
  4. 생명주기 이벤트: StateFlow를 보유하는 Android 구성 요소(Activity 또는 Fragment)가 파괴되거나 더 이상 필요하지 않을 때, onPause, onStop, onDestroy 또는 onDestroyView와 같은 생명주기 이벤트를 겪습니다. 메모리 누수를 방지하기 위해 생명주기 이벤트를 올바르게 처리하는 것이 중요합니다.
  5. 관찰 취소: 구성 요소가 더 이상 활성 상태일 때 StateFlow의 관찰을 취소하여 리소스 누수 및 불필요한 업데이트를 방지하는 것이 좋습니다.
    override fun onCleared() {
        super.onCleared()
        // 모든 활성 코루틴, including StateFlow 관찰 취소
        viewModelScope.cancel()
    }
  6. 정리: Android 구성 요소의 적절한 생명주기 이벤트(예: onDestroy 또는 onDestroyView)에서 StateFlow와 관련된 정리 작업을 수행할 수 있습니다.

MutableStateFlow와 mutableStateOf

MutableStateFlowmutableStateOf는 모두 상태를 관리하는 데 사용되지만, 각각의 사용 목적과 위치가 다르기 때문에 헷갈리지 말자.

1. MutableStateFlow

  • 사용 위치: 주로 ViewModel과 같은 데이터 관리 구성 요소에서 사용된다.
  • 목적: 복잡한 앱 상태를 관리하고, 여러 Composable 함수 간에 상태를 공유하는 데 유용하다.
    상태가 변경될 때 UI를 자동으로 업데이트한다.
  • 생명주기: ViewModel의 생명주기에 따라 관리되며, UI 레이어와 분리되어 있다.
  • 기능: 여러 수집자(collectors)에게 상태를 전달하고, 상태 변화를 관찰할 수 있다.

2. mutableStateOf

  • 사용 위치: Composable 함수 내에서 주로 사용된다.
  • 목적: 간단한 로컬 상태를 관리하는 데 유용하다. 특정 Composable에서만 사용되고, 그에 따라 UI를 즉시 업데이트한다.
  • 생명주기: Composable 함수의 생명주기와 함께 자동으로 관리된다.
  • 기능: 해당 Composable 내에서 상태 변화를 쉽게 추적하고 관리할 수 있다.

요약

  • MutableStateFlow는 복잡한 앱의 전역 상태를 관리하기 위해 ViewModel에서 사용된다. 여러 UI 구성 요소 간에 상태를 공유하고, 비즈니스 로직을 분리하는 데 도움을 준다.
  • mutableStateOf는 특정 Composable에서만 필요한 간단한 상태를 관리한다. 주로 UI 요소의 상태에 사용된다.

예시로 이해하기

  1. MutableStateFlow는 데이터베이스에서 사용자 정보를 가져오는 ViewModel에서 사용되어, 여러 화면에서 동일한 사용자 이름을 공유할 수 있도록 도와준다.
  2. mutableStateOf는 특정 Composable 내에서 다양한 UI 상태(예: 텍스트 필드의 내용, 체크박스의 선택 상태, 드롭다운 메뉴의 선택 항목 등)를 관리한다. 이 상태는 사용자 상호작용이나 내부 로직에 의해 업데이트될 수 있으며, 변경 시 관련 UI 요소가 자동으로 리컴포즈된다.

StateFlow가 LiveData보다 더 효율적인 이유

StateFlow와 LiveData는 Android 개발에서 데이터의 변경을 관찰하고 반응하는 유사한 목적을 가지고 있습니다. 두 가지 모두 유용하지만, StateFlow는 특정 장점으로 인해 더 효율적이고 선호되는 경우가 있습니다.

  • Kotlin 코루틴과의 통합이 원활하게 이루어져 비동기 작업을 처리할 수 있습니다. 이를 통해 비동기 데이터 흐름을 보다 자연스럽고 간결하게 다룰 수 있습니다.
  • StateFlow는 비동기 데이터 스트림을 작업하는 데 더 강력하고 유연한 API를 제공합니다. map, filter 및 transform과 같은 다양한 연산자를 사용하여 데이터 스트림을 쉽게 조작하고 변환할 수 있습니다.
  • 역압(back-pressure) 처리를 위한 내장 지원이 있어 데이터 방출 및 소비 속도를 제어할 수 있습니다. LiveData는 이러한 기능을 제공하지 않아 역압 제어가 중요한 시나리오에서 덜 적합합니다.
  • StateFlow는 변경 가능한(mutable) 및 불변(immutable) 변형이 모두 제공됩니다. MutableStateFlow를 사용하면 상태를 업데이트할 수 있지만, StateFlow(불변)는 읽기만 가능합니다.
  • StateFlow는 Android 생명주기에 묶여 있지 않습니다. LiveData는 생명주기 인식으로 설계되어 관련 구성 요소가 파괴될 때 자동으로 정리되지만, StateFlow는 이러한 요구 사항이 없습니다. 이는 UI가 아닌 구성 요소나 긴 수명 배경 작업에서 데이터를 관찰해야 할 때 장점이 될 수 있습니다.
  • StateFlow는 Kotlin 코루틴의 테스트 유틸리티를 사용하여 쉽게 테스트할 수 있어, StateFlow를 사용하는 코드에 대한 단위 테스트를 작성하는 것이 간단합니다. LiveData는 테스트할 수 있지만, 더 많은 설정과 테스트 보일러플레이트가 필요할 수 있습니다.

StateFlow와 LiveData 중 선택은 특정 프로젝트 요구 사항과 Kotlin Flow 및 코루틴을 개발 스택의 일부로 채택할 것인지에 따라 다릅니다. 많은 경우, 비동기 데이터 스트림 작업에서의 유연성과 효율성 덕분에 StateFlow가 선호됩니다.

MutableStateFlow의 생성

MutableStateFlow는 초기 값과 함께 MutableStateFlow(value) 생성자 함수를 사용하여 생성됩니다. mutable state flow의 값은 value 속성을 설정하여 업데이트할 수 있습니다. 값의 업데이트는 항상 동시성을 고려하여 발생합니다. 따라서 느린 수집자는 빠른 업데이트를 건너뛰지만, 항상 가장 최근에 방출된 값을 수집합니다.

class CounterModel {
    private val _counterData = MutableStateFlow(0) // private mutable state flow
    val counterData = _counterData.asStateFlow() // publicly exposed as read-only state flow

    fun inc() {
        _counterData.update { count -> count + 1 } // atomic, safe for concurrent use
    }
}

StateFlow의 단점

  1. StateFlow에서 수집 시 NullPointerException이 발생할 수 있습니다.
  2. StateIn 연산자는 StateFlow의 캐시된 값을 업데이트하지 않습니다.
  3. 값에 접근할 때 모든 StateFlow의 값이 계산되므로, 계산 중에 무거운 작업이 발생하면 문제가 될 수 있습니다.

Android Kotlin에서의 StateFlow 사용방법

  1. 앱의 build.gradle 파일에 필요한 종속성을 추가합니다:

    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2"
    implementation "androidx.lifecycle:lifecycle-viewmodel:2.3.1"
    implementation "androidx.lifecycle:lifecycle-livedata:2.3.1"
  2. StateFlow를 보유할 UI 구성 요소의 ViewModel 클래스를 생성합니다.

    import androidx.lifecycle.ViewModel
    import androidx.lifecycle.viewModelScope
    import kotlinx.coroutines.flow.MutableStateFlow
    import kotlinx.coroutines.flow.StateFlow
    import kotlinx.coroutines.launch
    
    class MainViewModel : ViewModel() {
        private val _myStateFlow = MutableStateFlow<String>("Initial Value")
        val myStateFlow: StateFlow<String> = _myStateFlow
    
        fun updateUIState(newValue: String) {
            viewModelScope.launch {
                _myStateFlow.value = newValue
            }
        }
    }
  3. Activity 또는 Fragment에서 ViewModel의 인스턴스를 생성하고 StateFlow를 관찰합니다.

    import androidx.lifecycle.ViewModelProvider
    import kotlinx.coroutines.flow.collect
    
    class MainActivity : AppCompatActivity() {
        private lateinit var viewModel: MainViewModel
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
    
            viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
    
            viewModel.myStateFlow.collect
            

출처: https://medium.com/@paritasampa95/stateflow-in-android-812e4d82cac5

해당 글은 번역한 글이며, 본인이 작성한 게시글이 아님!


state(상태), remember, MutableState

Jetpack Compose는 선언형 UI이다.
Jetpack Compose에서 StateFul한, 즉 데이터 변경 가능성이 있는 UI에 데이터를 Binding하기 위해서는 State(상태)라는 개념을 활용하여 데이터를 갱신 해주어야 데이터 최신화가 가능하다.

Jetpack Compose는 이러한 State(상태)를 보다 명확하게 관리하도록 도와주는 다양한 API를 제공한다. remeber는 Jetpack Compose에서 State와 State를 관리하는 핵심 기능 중 하나이다.

State and composition

Jetpack Compose에서의 상태 관리는 여러 방식으로 이루어지며, 이 중에서 remember와 mutableStateOf API는 Composable 함수에서 상태를 효과적으로 다루는 데 중요한 역할을 한다.

상태란?

상태란, 앱에서 시간에 따라 변할 수 있는 모든 값을 의미한다.

ex)
네트워크 연결이 설정되지 않았을 때 나타나는 스낵바
블로그 포스트와 관련 댓글
사용자가 클릭할 때 재생되는 버튼의 리플 애니메이션
이미지 위에 사용자가 그릴 수 있는 스티커

특히 Compose는 선언형 UI 이므로 이를 업데이트하는 유일한 방법은 새 인수로 동일한 컴포저블을 호출하는 것이다.

새 인수로 컴포저블을 다시 호출하게 된다면 상태가 업데이트될 때마다 recomposition(재구성)이 발생한다.

예시 코드

@Composable
private fun HelloContent() {
    Column(modifier = Modifier.padding(16.dp)) {
        Text(
            text = "Hello!",
            modifier = Modifier.padding(bottom = 8.dp),
            style = MaterialTheme.typography.bodyMedium
        )
        OutlinedTextField(
            value = "",
            onValueChange = { },
            label = { Text("Name") }
        )
    }
}

위 코드는 XML 기반의 명령형 뷰에서처럼 TextField와 같은 요소들이 자동으로 업데이트되지 않는다. 컴포저블은 새로운 상태를 명시적으로 전달받아야 해당 상태에 맞게 업데이트될 수 있다.

그 이유는 TextField 컴포저블이 자체적으로 업데이트되지 않고 value 매개변수가 변경될 때 업데이트되기 때문이다. 이는 Compose에서 Composition 및 Recomposition 작동하는 방식 때문이다.

용어 정리
- 구성(Composition): 
Compose 함수들이 실행되어 UI 요소를 트리 구조로 구성하는 것

- 초기 구성(Initial Composition):
컴포저블 함수들이 처음 실행될 때 일어나는 과정.
이때, Compose는 주어진 컴포저블 함수들을 통해 애플리케이션의 UI를 구축하고, 
각 UI 요소를 적절한 위치와 함께 트리에 추가한다.

- 재구성(Recomposition):
데이터나 상태가 변경될 때, Compose는 영향 받은 UI 부분만을 효율적으로 업데이트하기 위해 
해당 컴포저블 함수를 다시 실행한다.
이 과정을 통해 앱은 최신 상태를 반영하여 사용자에게 보여질 수 있다.

remember의 역할

Composable에서 State를 관리하는 방법은 대표적으로 remember가 있다.
remember API의 기본적인 기능은 객체를 메모리에 저장하는 기능이다.

이를 통해 remember는 컴포저블이 초기 구성(Initial Composition)에서 실행될 때 계산된 값을 "기억"하고, 재구성 시에 이 값을 유지하여 반환한다. 이는 Compose의 효율성을 높여주는 메커니즘으로, 불필요한 계산을 방지하고 성능을 최적화한다.

따라서, remember를 사용할 때는 그것이 재구성 시에 다시 호출되지 않고, 초기에 저장된 값을 계속해서 반환한다는 점을 이해하는 것이 중요하다.
이는 상태 관리를 효과적으로 돕고, Compose UI의 성능을 유지하는 데 도움을 제공한다.

remember는 해당 객체를 Composition에 저장하며, remember를 호출한 컴포저블이 Composition에서 제거되면 저장된 객체를 "잊어버린다"(즉, 객체를 메모리에서 제거한다).

MutableState의 사용

mutableStateOf는 변화 가능한 상태를 생성하고, 이 상태가 변경될 때 관련된 컴포저블 함수들을 재구성하도록 한다. 이는 Compose 런타임과 통합된 관찰 가능한 타입(MutableState)을 통해 이루어진다.

MutableState의 Recomposition(재구성) 유도

MutableState는 어떻게 상태가 변경된 것을 컴포저블에 알리고 함수를 재구성 할 수 있도록 하는걸까?

mutableStateOf를 통해 생성된 MutableState 객체가 컴포저블 함수들을 재구성(recomposition)할 수 있는 주된 이유는, 이 객체가 관찰 가능한 상태(observable state)를 제공하기 때문이다.

관찰 가능한 상태 (Observable State)

MutableState는 상태의 변화를 자동으로 감지하고 이에 반응할 수 있는 구조로 설계되어 있다.
이 객체의 value 속성에 변화가 발생하면, Compose 런타임은 그 변화를 감지하고 value를 사용하는 모든 컴포저블 함수들을 자동으로 재구성하도록 스케줄링한다.

이러한 방식은 다음과 같은 흐름으로 작동한다.

  1. 상태 변경 감지: MutableState 내의 value가 변경될 때, 이 상태 객체는 등록된 리스너들(여기서는 컴포저블 함수들)에게 변화를 알린다.

  2. Recomposition 트리거: 상태를 사용하는 컴포저블 함수들은 변화를 통지받고, 필요에 따라 자신을 재구성하기 위한 요청을 Compose 런타임에 전송한다.

  3. 최소한의 UI 업데이트: Compose는 선언적 UI 접근 방식을 사용하여, 실제로 변경이 필요한 UI 부분만을 업데이트한다. 이는 전체 UI를 다시 그리는 대신, 변경된 데이터에 의존하는 UI 요소만을 재구성하여 성능을 최적화한다.

MutableState 객체를 선언하는 방법은 아래와 같이 세 가지가 있다.

val mutableState = remember { mutableStateOf(default) }
var value by remember { mutableStateOf(default) }
val (value, setValue) = remember { mutableStateOf(default) }

간단히 짚어보는 구조 분해 선언 (componentN 함수들의 사용)

Kotlin에서 구조 분해 선언은 복합적인 값을 각각의 변수로 분해하여 할당할 수 있게 해준다.
각 요소를 개별 변수에 할당하려면 해당 타입은 component1(), component2(), 등의 메서드를 제공해야 한다.

MutableState는 아래와 같은 두 개의 메서드를 통해 구조 분해를 지원한다.

  • component1() 함수는 MutableState의 value를 반환한다. 이는 상태의 현재 값을 얻는 데 사용된다.
  • component2() 함수는 value의 값을 설정하는 함수를 반환한다. 이는 상태를 업데이트할 때 사용되는 세터(setter) 함수다.

이 두 함수 덕분에, MutableState 객체는 구조 분해를 통해 value와 세터 함수를 쉽게 할당할 수 있게 되며, 이는 코드의 가독성과 사용의 용이성을 높인다.

예시코드

@Composable
fun Counter() {
    // MutableState를 구조 분해하여 value와 setter를 얻습니다.
    val (count, setCount) = remember { mutableStateOf(0) }

    Column(
        modifier = androidx.compose.ui.Modifier.padding(16.dp)
    ) {
        Text(text = "Count: $count")

        Spacer(modifier = androidx.compose.ui.Modifier.height(8.dp))

        Button(onClick = { setCount(count + 1) }) {
            Text("Increase Count")
        }
    }
}

MutableState의 구조 분해에 사용된 component1()component2() 함수는 Kotlin의 내부적인 메커니즘 덕분에 눈에 보이지 않지만 자동으로 제공된다.

즉, remember { mutableStateOf(0) }로 반환된 MutableState 객체는 내부적으로 component1() 함수로 현재 값(value)을 반환하고, component2() 함수로 값 설정을 위한 setter 함수를 제공하는 것이다.

이 두 메서드는 MutableState에서 이미 정의되어 있어서, 코드에서 직접 보이지 않지만 구조 분해를 사용할 수 있는 것이다.

구조 분해의 동작 원리

val (count, setCount) = remember { mutableStateOf(0) }

이 코드에서는 다음과 같은 일이 벌어진다.

  1. remember { mutableStateOf(0) }가 호출되어 MutableState<Int> 객체가 반환된다.
  2. 구조 분해를 통해 component1() 메서드가 호출되어 value 값(현재 상태 값, 즉 count)이 반환된다.
  3. component2() 메서드가 호출되어 setValue (즉, 상태를 업데이트하는 함수)도 반환된다.

따라서 count는 상태의 현재 값이고, setCount는 그 값을 업데이트하는 함수가 된다.

remember와 rememberSaveable의 차이

remember는 Recomposition을 거쳐도 상태를 유지하지만, Configuration Change 즉, 구성 변경(예: 화면 회전)이 발생하면 상태를 유지하지 않는다.
이에 대한 해결책으로 rememberSaveable을 사용하면 된다.

rememberSaveable은 자동으로 Bundle에 저장될 수 있는 값들을 저장하며, 커스텀 저장 객체를 통해 다른 유형의 값들도 저장할 수 있다.

과정 시각화

  1. Composable 함수: UI를 구성하는 함수가 실행된다.

  2. remember: 이전 상태를 기억하거나 새로운 상태를 생성한다.

  3. MutableState 객체 생성 및 저장: remember를 통해 MutableState 객체가 생성되고 메모리에 저장된다.

  4. 변경 감지: MutableState 객체의 값이 변경되면 이를 감지한다

  5. 컴포저블 재호출: 상태 변경으로 인해 새로운 인수를 가지고 Composable 함수가 다시 호출된다.

  6. Recomposition: Compose 런타임이 UI 트리를 재구성한다.

  7. 변경된 부분만 업데이트: 실제로 변경된 부분만 효율적으로 UI를 업데이트한다.

  8. 반복: 이 과정이 새로운 상태 변경이 있을 때마다 반복된다.

주의사항

가변 객체를 상태로 사용하는 것은 데이터가 잘못되거나 오래된 것으로 보이게 할 수 있으므로,
가능하면 State<List>와 같은 관찰 가능한 데이터 홀더와 listOf()와 같은 불변 컬렉션을 사용하는 것이 좋다.

더 나아가 ImmutableList을 사용하는 것도 좋다.
Compose에서는 List또한 안정된 타입이 아니기에 불필요한 리컴포지션이 발생할 가능성이 있기 때문이다.

StateFlow Vs. remember, mutableState

1. StateFlow와 remember, MutableState의 공통점과 차이점

공통점

  • 상태 관리: 둘 다 상태를 관리하고 관찰할 수 있는 기능을 제공하여, 값이 변경되면 UI가 자동으로 갱신되도록 한다.
  • 리액티브 프로그래밍: 둘 다 리액티브하게 상태 변화를 감지하고 그에 맞춰 UI를 업데이트하는 데 사용된다.
  • Kotlin 기반: Kotlin의 특성을 기반으로 하여 동작하며, 코루틴과 함께 사용할 수 있다.

차이점

  • 사용되는 범위

    • StateFlow: ViewModel 또는 비즈니스 로직처럼 Composable 함수 외부에서 상태를 관리하는 데 사용된다. 주로 UI와 독립적인 데이터 계층에서 사용된다.
    • remember & MutableState: Composable 함수 내에서만 사용되며, UI 상태를 직접 관리하기 위해 사용된다.
  • 핫/콜드 플로우

    • StateFlow: 핫 플로우로, 활성 인스턴스가 수집자 없이도 메모리에 존재하며, 값이 변경될 때마다 최신 상태를 항상 유지한다.
    • MutableState: Compose의 상태 관리 시스템에서 리컴포지션이 발생할 때만 값을 추적하며, Composable 함수의 생명주기와 밀접하게 관련된다.
  • 구현 방식

    • StateFlow: Kotlin Flow의 일부로, 코루틴과 함께 비동기 작업을 관리할 수 있으며, 복잡한 비즈니스 로직에서도 사용 가능하다.
    • MutableState: 간단한 상태 관리에 최적화되어 있으며, 상태 값이 변경되면 해당 Composable을 리컴포지션하여 UI를 갱신하는 용도로 주로 사용된다.

StateFlow는 ViewModel에서, rememberMutableState는 Composable 함수에서 사용되는 이유

StateFlow가 ViewModel에서 사용되는 이유 (ViewModel 생명주기 기반)

  • ViewModel의 생명주기는 화면 회전과 같은 UI의 구성 변경에도 영향을 받지 않고 데이터를 유지할 수 있다. ViewModel은 화면이 파괴되더라도 다시 생성되기 전까지 데이터를 유지하기 때문에, 이를 통해 UI 상태를 유지하고 업데이트하는 데 적합하다.

  • StateFlow는 ViewModel의 상태를 생명주기와 상관없이 지속적으로 유지하고, UI 컴포넌트가 존재하지 않아도 데이터를 지속적으로 관리한다. ViewModel은 비즈니스 로직, 백엔드 API 통신 등 UI와 독립적인 상태를 관리하므로 가능한 것이다.

  • 코루틴 기반 비동기 처리: StateFlow는 코루틴과 함께 동작하여 비동기 작업 처리에 유용하다. 비동기 작업을 수행할 때 UI가 없어도 작업이 계속 진행되고, 나중에 UI가 다시 나타났을 때도 상태를 유지하고 업데이트할 수 있다.

  • UI와 별도의 데이터 흐름: ViewModel의 UI와 독립적인 데이터 흐름을 관리하는데, StateFlow는 이러한 데이터 흐름을 적절히 관리하고, ViewModel의 생명주기 동안 데이터를 안정적으로 유지한다.

rememberMutableState가 Composable 함수에서 사용되는 이유 (Composable 생명주기 기반)

  • Composable 함수의 생명주기는 컴포넌트의 상태가 리컴포지션(재구성)될 때마다 다시 실행된다. 하지만 상태를 리셋하지 않고 유지해야 하는 경우가 있는데, 이때 remember는 Composable 내에서 리컴포지션이 일어나더라도 상태를 유지할 수 있게 해준다.

  • remember는 Composable의 상태 관리: Composable 함수 내에서 remember는 컴포넌트의 상태를 기억하여 리컴포지션 때 초기화되지 않도록 한다. 따라서 UI 컴포넌트가 빠르게 상태 변화를 반영하는 데 적합하다. 이 방식은 UI에 직접적으로 영향을 미치는 데이터를 관리할 때 효율적이다.

  • MutableState는 UI 상태 갱신: MutableState는 Composable 함수 내에서 상태 변화에 따라 UI가 자동으로 갱신되도록 한다. 이는 UI에서 상태 변화에 따른 즉각적인 반응(버튼 클릭 후 카운트 증가 등)을 처리하는 데 적합하다.

  • Composable 함수와의 밀접한 관계: rememberMutableState는 Composable 함수 내부에서 UI와 밀접한 상태를 관리하고, 해당 상태가 변경될 때 즉각적으로 리컴포지션을 발생시킨다. 이는 UI 상태 변화에 빠르게 반응하고, 컴포저블 함수의 생명주기에 맞춰 상태를 관리할 수 있도록 설계되었다.

요약하면:

  • StateFlow는 ViewModel의 생명주기에 맞춰 UI와 독립적으로 상태를 유지하고 비동기 작업을 처리할 수 있기 때문에 ViewModel에서 주로 사용된다.

  • 반면 rememberMutableState는 Composable 함수 내에서 UI 상태를 관리하고, 리컴포지션에 맞춰 상태를 보존하면서 UI 갱신에 최적화된 상태 관리 방식이므로 Composable 함수에서 사용된다.

  • 그럼 안드로이드 개발자가 StateFlow는 UI 레이어가 아닌 것들 (ex.ViewModel)에서 사용하고 , remember와 MutableState는 UI 레이어(ex.Composable 함수)에서 사용하라고 의도하여 개발했다고 알면된다.

  • 비교

    • StateFlow:
      StateFlow는 원래 코틀린 코루틴의 Flow API의 확장으로 만들어진 것이다. 이는 반응형 프로그래밍을 지원하기 위한 것으로, 특정 플랫폼에 국한되지 않는 일반적인 코틀린 기능이다.

      안드로이드 개발팀은 이 기존의 코틀린 기능을 안드로이드 앱 아키텍처에 통합하였다.
      그들은 StateFlow가 ViewModel과 같은 UI 외부 레이어에서 상태를 관리하는 데 매우 적합하다고 판단했다. 안드로이드 개발팀은 StateFlow를 UI 레이어가 아닌 곳에서 사용하도록 권장하는 이유로, StateFlow의 특성(예: 구독자가 없어도 최신 값 유지, 효율적인 상태 변경 전파 등)이 ViewModel의 요구사항과 잘 맞기 때문이다.

      따라서, StateFlow 자체는 코틀린에서 만들어졌지만, 안드로이드 개발에서 특정 방식으로 사용하도록 권장되고 있는 것이다. 이는 안드로이드 개발팀이 기존의 강력한 코틀린 기능을 안드로이드 앱 아키텍처에 효과적으로 통합한 좋은 예시라고 볼 수 있다.

    • remember와 MutableState:
      remember와 MutableStateOf는 StateFlow와 달리 안드로이드 팀이 Jetpack Compose를 위해 특별히 만들고, UI 레이어에서 사용하도록 의도한 기능이다. 이는 Compose의 선언적 UI 모델과 완벽하게 통합되도록 설계되었으며, UI 상태 관리를 위한 최적의 도구로 제공되고 있다.

구현체 비교

카테고리구현체특징 및 용도
StateFlowStateFlow- 읽기 전용 상태 흐름
- 항상 값을 가지며, 초기값 필요
- 값이 변경될 때만 수집기에 알림
MutableStateFlow- 수정 가능한 StateFlow
- value 프로퍼티를 통해 값을 읽고 쓸 수 있음
- emit() 함수로 값 업데이트 가능
SharedFlow- 여러 수집기에 값을 브로드캐스트하는 핫 플로우
- 초기값이 없을 수 있고, 이전 값들을 버퍼링 가능
MutableSharedFlow- 수정 가능한 SharedFlow
- emit() 함수로 값을 내보낼 수 있음
remember+MutableStatemutableStateOf- Compose에서 관찰 가능한 값 생성
- 값이 변경되면 사용 중인 Composable 함수 재구성
rememberSaveable- 구성 변경 시에도 상태 유지
- 번들에 저장 가능한 값에 사용
derivedStateOf- 다른 상태를 기반으로 계산된 상태 생성
- 계산 비용이 높은 작업에 유용
produceState- 비동기 데이터 소스로부터 State 생성
- 코루틴 범위에서 값을 생성하고 내보냄

정리

특성StateFlow (ViewModel)remember/MutableState (Composable)
관심사의 분리비즈니스 로직과 데이터 처리에 집중UI 렌더링과 로컬 상태 관리에 집중
생명주기 관리ViewModel의 긴 생명주기에 맞춤
설정 변경 시에도 데이터 유지
Composable의 생명주기에 맞춰 자동 관리
메모리 누수 방지
재사용성여러 화면에서 재사용 가능특정 Composable에 국한된 사용
테스트 용이성단위 테스트 수행이 용이UI 테스트와 통합하여 테스트
성능 최적화여러 구독자에게 효율적으로 상태 전파Compose의 재구성 최적화와 통합
불필요한 UI 업데이트 방지
상태 공유여러 Composable 간 상태 공유 용이주로 단일 Composable 내 상태 관리
복잡성 관리복잡한 앱 상태 관리에 적합간단한 로컬 UI 상태 관리에 적합
적용 클래스UI 레이어가 아닌 곳, 특히 ViewModelUI 레이어

참고한 글:
https://velog.io/@kej_ad/AndroidCompose-state%EC%83%81%ED%83%9C%EC%99%80-remember-%EA%B7%B8%EB%A6%AC%EA%B3%A0-MutableState-%EB%9E%80

profile
화려한 외면이 아닌 단단한 내면

0개의 댓글

관련 채용 정보