StateFlow는 애플리케이션에서 상태를 관리하고 표현하기 위해 Kotlin Flow 라이브러리의 일부입니다.
StateFlow는 값이 업데이트된 경우에만 반환하며, 같은 값을 반복해서 반환하지 않습니다.
두 개의 값 a와 b를 고려해 보겠습니다. 여기서 a는 처음 방출된 값이고, b는 방출할 값입니다.
if (a == b) { do nothing }
else if (a != b) { return b }
StateFlow를 RxJava의 Subject와 비슷하게 생각할 수 있습니다.
StateFlow는 핫 플로우입니다. 이는 활성 인스턴스가 수집자의 존재와 상관없이 메모리에 존재한다는 의미입니다.
현재 값은 value 속성을 통해 검색할 수 있습니다.
Android에서 StateFlow는 관찰 가능한 변경 가능한 상태를 유지해야 하는 클래스에 적합합니다.
StateFlow는 Android 애플리케이션에서 상태를 관리하고 관찰하기 위해 특별히 설계되었습니다.
UI 관련 데이터를 관리하기 위한 LiveData의 대안으로 자주 사용됩니다.
상태에 대한 단일 진실의 출처를 유지하고, 모든 수집자가 최신 상태로 자동 업데이트되도록 할 때 StateFlow를 사용합니다.
StateFlow는 감독 제어, 작업 스케줄링, 오류 관리, 통신 프로토콜, 사용자 인터페이스 및 하이브리드 시스템을 설계하고 개발할 수 있게 해줍니다. Android에서 StateFlow는 관찰 가능한 변경 가능한 상태를 유지해야 하는 클래스에 적합합니다.
StateFlow의 생명주기는 일반적으로 Activity나 Fragment와 같은 그것을 보유하는 구성 요소의 생명주기를 따릅니다. StateFlow의 주요 목적은 데이터의 반응형 스트림을 제공하는 것이며, 그 행동은 관찰하는 구성 요소의 생명주기에 밀접하게 연결되어 있습니다.
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 // 상태 업데이트
}
}
viewModel.myStateFlow.collect { state ->
// 상태 변경 처리
}
viewModel.updateState("New Value")
override fun onCleared() {
super.onCleared()
// 모든 활성 코루틴, including StateFlow 관찰 취소
viewModelScope.cancel()
}
MutableStateFlow
와 mutableStateOf
는 모두 상태를 관리하는 데 사용되지만, 각각의 사용 목적과 위치가 다르기 때문에 헷갈리지 말자.
StateFlow와 LiveData는 Android 개발에서 데이터의 변경을 관찰하고 반응하는 유사한 목적을 가지고 있습니다. 두 가지 모두 유용하지만, StateFlow는 특정 장점으로 인해 더 효율적이고 선호되는 경우가 있습니다.
StateFlow와 LiveData 중 선택은 특정 프로젝트 요구 사항과 Kotlin Flow 및 코루틴을 개발 스택의 일부로 채택할 것인지에 따라 다릅니다. 많은 경우, 비동기 데이터 스트림 작업에서의 유연성과 효율성 덕분에 StateFlow가 선호됩니다.
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
}
}
앱의 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"
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
}
}
}
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
해당 글은 번역한 글이며, 본인이 작성한 게시글이 아님!
Jetpack Compose는 선언형 UI이다.
Jetpack Compose에서 StateFul한, 즉 데이터 변경 가능성이 있는 UI에 데이터를 Binding하기 위해서는 State(상태)라는 개념을 활용하여 데이터를 갱신 해주어야 데이터 최신화가 가능하다.
Jetpack Compose는 이러한 State(상태)를 보다 명확하게 관리하도록 도와주는 다양한 API를 제공한다. remeber는 Jetpack Compose에서 State와 State를 관리하는 핵심 기능 중 하나이다.
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 부분만을 효율적으로 업데이트하기 위해
해당 컴포저블 함수를 다시 실행한다.
이 과정을 통해 앱은 최신 상태를 반영하여 사용자에게 보여질 수 있다.
Composable에서 State를 관리하는 방법은 대표적으로 remember가 있다.
remember API의 기본적인 기능은 객체를 메모리에 저장하는 기능이다.
이를 통해 remember는 컴포저블이 초기 구성(Initial Composition)에서 실행될 때 계산된 값을 "기억"하고, 재구성 시에 이 값을 유지하여 반환한다. 이는 Compose의 효율성을 높여주는 메커니즘으로, 불필요한 계산을 방지하고 성능을 최적화한다.
따라서, remember를 사용할 때는 그것이 재구성 시에 다시 호출되지 않고, 초기에 저장된 값을 계속해서 반환한다는 점을 이해하는 것이 중요하다.
이는 상태 관리를 효과적으로 돕고, Compose UI의 성능을 유지하는 데 도움을 제공한다.
remember는 해당 객체를 Composition에 저장하며, remember를 호출한 컴포저블이 Composition에서 제거되면 저장된 객체를 "잊어버린다"(즉, 객체를 메모리에서 제거한다).
mutableStateOf는 변화 가능한 상태를 생성하고, 이 상태가 변경될 때 관련된 컴포저블 함수들을 재구성하도록 한다. 이는 Compose 런타임과 통합된 관찰 가능한 타입(MutableState)을 통해 이루어진다.
MutableState는 어떻게 상태가 변경된 것을 컴포저블에 알리고 함수를 재구성 할 수 있도록 하는걸까?
mutableStateOf를 통해 생성된 MutableState 객체가 컴포저블 함수들을 재구성(recomposition)할 수 있는 주된 이유는, 이 객체가 관찰 가능한 상태(observable state)를 제공하기 때문이다.
MutableState는 상태의 변화를 자동으로 감지하고 이에 반응할 수 있는 구조로 설계되어 있다.
이 객체의 value 속성에 변화가 발생하면, Compose 런타임은 그 변화를 감지하고 value를 사용하는 모든 컴포저블 함수들을 자동으로 재구성하도록 스케줄링한다.
이러한 방식은 다음과 같은 흐름으로 작동한다.
상태 변경 감지: MutableState 내의 value가 변경될 때, 이 상태 객체는 등록된 리스너들(여기서는 컴포저블 함수들)에게 변화를 알린다.
Recomposition 트리거: 상태를 사용하는 컴포저블 함수들은 변화를 통지받고, 필요에 따라 자신을 재구성하기 위한 요청을 Compose 런타임에 전송한다.
최소한의 UI 업데이트: Compose는 선언적 UI 접근 방식을 사용하여, 실제로 변경이 필요한 UI 부분만을 업데이트한다. 이는 전체 UI를 다시 그리는 대신, 변경된 데이터에 의존하는 UI 요소만을 재구성하여 성능을 최적화한다.
MutableState 객체를 선언하는 방법은 아래와 같이 세 가지가 있다.
val mutableState = remember { mutableStateOf(default) }
var value by remember { mutableStateOf(default) }
val (value, setValue) = remember { mutableStateOf(default) }
Kotlin에서 구조 분해 선언은 복합적인 값을 각각의 변수로 분해하여 할당할 수 있게 해준다.
각 요소를 개별 변수에 할당하려면 해당 타입은 component1(), component2(), 등의 메서드를 제공해야 한다.
MutableState는 아래와 같은 두 개의 메서드를 통해 구조 분해를 지원한다.
이 두 함수 덕분에, 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) }
이 코드에서는 다음과 같은 일이 벌어진다.
remember { mutableStateOf(0) }
가 호출되어 MutableState<Int>
객체가 반환된다.component1()
메서드가 호출되어 value
값(현재 상태 값, 즉 count
)이 반환된다.component2()
메서드가 호출되어 setValue
(즉, 상태를 업데이트하는 함수)도 반환된다.따라서 count
는 상태의 현재 값이고, setCount
는 그 값을 업데이트하는 함수가 된다.
remember는 Recomposition을 거쳐도 상태를 유지하지만, Configuration Change 즉, 구성 변경(예: 화면 회전)이 발생하면 상태를 유지하지 않는다.
이에 대한 해결책으로 rememberSaveable을 사용하면 된다.
rememberSaveable은 자동으로 Bundle에 저장될 수 있는 값들을 저장하며, 커스텀 저장 객체를 통해 다른 유형의 값들도 저장할 수 있다.
Composable 함수: UI를 구성하는 함수가 실행된다.
remember: 이전 상태를 기억하거나 새로운 상태를 생성한다.
MutableState 객체 생성 및 저장: remember
를 통해 MutableState
객체가 생성되고 메모리에 저장된다.
변경 감지: MutableState
객체의 값이 변경되면 이를 감지한다
컴포저블 재호출: 상태 변경으로 인해 새로운 인수를 가지고 Composable 함수가 다시 호출된다.
Recomposition: Compose 런타임이 UI 트리를 재구성한다.
변경된 부분만 업데이트: 실제로 변경된 부분만 효율적으로 UI를 업데이트한다.
반복: 이 과정이 새로운 상태 변경이 있을 때마다 반복된다.
가변 객체를 상태로 사용하는 것은 데이터가 잘못되거나 오래된 것으로 보이게 할 수 있으므로,
가능하면 State<List>
와 같은 관찰 가능한 데이터 홀더와 listOf()와 같은 불변 컬렉션을 사용하는 것이 좋다.
더 나아가 ImmutableList을 사용하는 것도 좋다.
Compose에서는 List또한 안정된 타입이 아니기에 불필요한 리컴포지션이 발생할 가능성이 있기 때문이다.
remember
, MutableState
의 공통점과 차이점사용되는 범위
StateFlow
: ViewModel 또는 비즈니스 로직처럼 Composable 함수 외부에서 상태를 관리하는 데 사용된다. 주로 UI와 독립적인 데이터 계층에서 사용된다.remember
& MutableState
: Composable 함수 내에서만 사용되며, UI 상태를 직접 관리하기 위해 사용된다.핫/콜드 플로우
StateFlow
: 핫 플로우로, 활성 인스턴스가 수집자 없이도 메모리에 존재하며, 값이 변경될 때마다 최신 상태를 항상 유지한다.MutableState
: Compose의 상태 관리 시스템에서 리컴포지션이 발생할 때만 값을 추적하며, Composable 함수의 생명주기와 밀접하게 관련된다.구현 방식
StateFlow
: Kotlin Flow의 일부로, 코루틴과 함께 비동기 작업을 관리할 수 있으며, 복잡한 비즈니스 로직에서도 사용 가능하다.MutableState
: 간단한 상태 관리에 최적화되어 있으며, 상태 값이 변경되면 해당 Composable을 리컴포지션하여 UI를 갱신하는 용도로 주로 사용된다.remember
와 MutableState
는 Composable 함수에서 사용되는 이유
ViewModel의 생명주기는 화면 회전과 같은 UI의 구성 변경에도 영향을 받지 않고 데이터를 유지할 수 있다. ViewModel은 화면이 파괴되더라도 다시 생성되기 전까지 데이터를 유지하기 때문에, 이를 통해 UI 상태를 유지하고 업데이트하는 데 적합하다.
StateFlow는 ViewModel의 상태를 생명주기와 상관없이 지속적으로 유지하고, UI 컴포넌트가 존재하지 않아도 데이터를 지속적으로 관리한다. ViewModel은 비즈니스 로직, 백엔드 API 통신 등 UI와 독립적인 상태를 관리하므로 가능한 것이다.
코루틴 기반 비동기 처리: StateFlow는 코루틴과 함께 동작하여 비동기 작업 처리에 유용하다. 비동기 작업을 수행할 때 UI가 없어도 작업이 계속 진행되고, 나중에 UI가 다시 나타났을 때도 상태를 유지하고 업데이트할 수 있다.
UI와 별도의 데이터 흐름: ViewModel의 UI와 독립적인 데이터 흐름을 관리하는데, StateFlow는 이러한 데이터 흐름을 적절히 관리하고, ViewModel의 생명주기 동안 데이터를 안정적으로 유지한다.
remember
와 MutableState
가 Composable 함수에서 사용되는 이유 (Composable 생명주기 기반)
Composable 함수의 생명주기는 컴포넌트의 상태가 리컴포지션(재구성)될 때마다 다시 실행된다. 하지만 상태를 리셋하지 않고 유지해야 하는 경우가 있는데, 이때 remember
는 Composable 내에서 리컴포지션이 일어나더라도 상태를 유지할 수 있게 해준다.
remember
는 Composable의 상태 관리: Composable 함수 내에서 remember
는 컴포넌트의 상태를 기억하여 리컴포지션 때 초기화되지 않도록 한다. 따라서 UI 컴포넌트가 빠르게 상태 변화를 반영하는 데 적합하다. 이 방식은 UI에 직접적으로 영향을 미치는 데이터를 관리할 때 효율적이다.
MutableState
는 UI 상태 갱신: MutableState
는 Composable 함수 내에서 상태 변화에 따라 UI가 자동으로 갱신되도록 한다. 이는 UI에서 상태 변화에 따른 즉각적인 반응(버튼 클릭 후 카운트 증가 등)을 처리하는 데 적합하다.
Composable 함수와의 밀접한 관계: remember
와 MutableState
는 Composable 함수 내부에서 UI와 밀접한 상태를 관리하고, 해당 상태가 변경될 때 즉각적으로 리컴포지션을 발생시킨다. 이는 UI 상태 변화에 빠르게 반응하고, 컴포저블 함수의 생명주기에 맞춰 상태를 관리할 수 있도록 설계되었다.
요약하면:
StateFlow
는 ViewModel의 생명주기에 맞춰 UI와 독립적으로 상태를 유지하고 비동기 작업을 처리할 수 있기 때문에 ViewModel에서 주로 사용된다.
반면 remember
와 MutableState
는 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 상태 관리를 위한 최적의 도구로 제공되고 있다.
카테고리 | 구현체 | 특징 및 용도 |
---|---|---|
StateFlow | StateFlow | - 읽기 전용 상태 흐름 - 항상 값을 가지며, 초기값 필요 - 값이 변경될 때만 수집기에 알림 |
MutableStateFlow | - 수정 가능한 StateFlow - value 프로퍼티를 통해 값을 읽고 쓸 수 있음 - emit() 함수로 값 업데이트 가능 | |
SharedFlow | - 여러 수집기에 값을 브로드캐스트하는 핫 플로우 - 초기값이 없을 수 있고, 이전 값들을 버퍼링 가능 | |
MutableSharedFlow | - 수정 가능한 SharedFlow - emit() 함수로 값을 내보낼 수 있음 | |
remember+MutableState | mutableStateOf | - Compose에서 관찰 가능한 값 생성 - 값이 변경되면 사용 중인 Composable 함수 재구성 |
rememberSaveable | - 구성 변경 시에도 상태 유지 - 번들에 저장 가능한 값에 사용 | |
derivedStateOf | - 다른 상태를 기반으로 계산된 상태 생성 - 계산 비용이 높은 작업에 유용 | |
produceState | - 비동기 데이터 소스로부터 State 생성 - 코루틴 범위에서 값을 생성하고 내보냄 |
특성 | StateFlow (ViewModel) | remember/MutableState (Composable) |
---|---|---|
관심사의 분리 | 비즈니스 로직과 데이터 처리에 집중 | UI 렌더링과 로컬 상태 관리에 집중 |
생명주기 관리 | ViewModel의 긴 생명주기에 맞춤 설정 변경 시에도 데이터 유지 | Composable의 생명주기에 맞춰 자동 관리 메모리 누수 방지 |
재사용성 | 여러 화면에서 재사용 가능 | 특정 Composable에 국한된 사용 |
테스트 용이성 | 단위 테스트 수행이 용이 | UI 테스트와 통합하여 테스트 |
성능 최적화 | 여러 구독자에게 효율적으로 상태 전파 | Compose의 재구성 최적화와 통합 불필요한 UI 업데이트 방지 |
상태 공유 | 여러 Composable 간 상태 공유 용이 | 주로 단일 Composable 내 상태 관리 |
복잡성 관리 | 복잡한 앱 상태 관리에 적합 | 간단한 로컬 UI 상태 관리에 적합 |
적용 클래스 | UI 레이어가 아닌 곳, 특히 ViewModel | UI 레이어 |