Coroutines Flow ๊ฒ์๊ธ ์๋ฆฌ์ฆ
- ๊ธฐ์ด
- Jetpack Compose์์ ์ฌ์ฉํ๊ธฐ
- Back pressure์ ๊ทธ ์ฒ๋ฆฌ
- Intermediate operator
- StateFlow
- Flow.combine()๊ณผ Flow.stateIn()
#1 ๊ฐ์
[Kotlin] Coroutines Flow - ๊ธฐ์ด
#1 Coroutines Flow#1-1 ๊ฐ์ FlowFlow An asynchronous data stream that sequentially emits values and completes normally or with an exception. Intermediate operators on the flow such as map, filter, take, zip, etc are functions that are applied to the upst
kenel.tistory.com
์์์ ๋ค๋ฃฌ Coroutines Flow๋ฅผ Jetpack Compose์ ์ ์ฉ์์ผ๋ณธ๋ค. ์ฌ๊ธฐ์ ๋งํ๋ '์ ์ฉ'์ด๋, ๋จ์ํ ์ฌ์ฉ์ด ์๋๋ผ Flow ๊ฐ์ฒด๋ฅผ State๋ก์ ๋ค๋ฃฌ๋ค๋ ์๋ฏธ๋ค.
#2 ์์ ํ ์ํ ์ฝ๋
...
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// State ์ ์ธ
val count = mutableStateOf(1)
// State์ ๊ฐ ๋ณ๊ฒฝ
CoroutineScope(Dispatchers.Default).launch {
for (i in 1..5) {
delay(1000)
count.value = i
}
}
setContent {
Box(
modifier = Modifier.fillMaxSize(),
) {
TextExample(
count.value, // State Hoisting (https://kenel.tistory.com/186)
Modifier.align(Alignment.Center)
)
}
}
}
}
@Composable
fun TextExample(
currentCount: Int,
modifier: Modifier = Modifier,
) {
Text(
text = "Count: $currentCount",
fontSize = 40.sp,
modifier = modifier
)
}
State๊ฐ ์ฐ์ธ ๊ฐ๋จํ ์ฝ๋๋ค. ์ด ์ฑ์ ์คํ์ํค๋ฉด, Count: 1์ผ๋ก ์์ํด Count: 5๊น์ง ํ์ํ๋ Text๊ฐ ํ๋ฉด ์ค์์ ๋ณด์ผ ๊ฒ์ด๋ค. ์ด์ ์ด ์ฝ๋์ ๊ฐ์ ๋์์ ํ๋ ์ฝ๋๋ฅผ Flow๋ฅผ ํ์ฉํด์ ์ง๋ณด๊ฒ ๋ค.
#3 Flow๋ก ๊ฐ์ ๋์ ๊ตฌํํ๊ธฐ
#3-1 ์ฝ๋
...
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Flow ์ ์ธ
val countFlow = flow {
for (i in 1..5) {
delay(2000)
emit(i)
}
}
setContent {
Log.i("interfacer_han", "Recomposition ...")
Box(
modifier = Modifier.fillMaxSize(),
) {
// collectAsState()
val count = countFlow.collectAsState(initial = 1)
TextExample(
count.value, // State Hoisting (https://kenel.tistory.com/186)
Modifier.align(Alignment.Center)
)
}
}
}
}
@Composable
fun TextExample(...) { ... }
Flow๋ฅผ ํ์ฉํด #2์ ์ฝ๋๋ฅผ ๋ค์ ์ง๋ฉด ์ด๋ฐ ์ฝ๋๊ฐ ๋๋ค. Flow๋ฅผ ๋ง๋ค๊ณ , collect()๋ฅผ ์ํํ๋ค. ์ด ๋ Jetpack Compose์ ๊ด๋ จ๋ ์์
์์๋, ๋ค์ ๋งํด UI์ ๊ด๋ จ๋ ์์
์์๋ collect()๊ฐ ์๋ collectAsState()๋ฅผ ์ฌ์ฉํ๋ค. collectAsState()๋ State ํ์
์ ๋ฐํํ๋ collect()๋ค. ๋จ์ํ ๋ฐํํ ๋ฟ๋ง ์๋๋ผ, collect()๋ ๊ฐ์ UI์ ํ์ํ๊ธฐ ์ํ ์ฌ๋ฌ๊ฐ์ง ์์์ ์ธ ๋์๋ค์ด ์ ์๋์ด์๋ ํจ์๋ค.
๊ทธ๋ฆฌ๊ณ count๊ฐ TextExample์์ ์ฐธ์กฐ๋๊ณ ์์ผ๋ฏ๋ก, ๋ค์ ๋งํด ๋ฐ์ดํฐํ์ด State์ธ ํ๋กํผํฐ๊ฐ UI์์ ์ฐธ์กฐ๋๊ณ ์์ผ๋ฏ๋ก, count.value์ ๊ฐ์ด ๋ณํ๋ฉด Recomposition์ด ์์์ ์ผ๋ก ์ํ๋ ๊ฒ์ด๋ค (์ด ๊ฒ์๊ธ์ #4 ์ฐธ์กฐ).
#3-2 remember์ ๋ํ ์๋ฌธ์ ๊ณผ ์ค๋ช
#3-1์ ์ฝ๋๋ฅผ ๋ณด๋ฉด ์ด๋ฐ ์๊ฐ์ด ๋ค ์ ์๋ค. Recomposition์ด ์ผ์ด๋๋ค๋ ๊ฒ์ setContent { ... } ์์ ์์นํ count๋ ์ด๊ธฐํ๋๋ค๋ ๋ง์ด ์๋๊ฐ? ์ฆ, countFlow.collectAsState(initial = 1)๋ฅผ remember { ... }๋ก ๊ฐ์ธ Recomposition์ ๋ํ ๋ฐฉ์ด๋ง์ ์ณ๋์ง ์์ผ๋ฉด count ์ด๊ธฐํ, Recomposition, count ์ด๊ธฐํ, Recomposition, count ์ด๊ธฐํ, ...์ ๋ฌดํ ๋ฃจํ๊ฐ ์ผ์ด๋๋ ๊ฒ ์๋๊ฐ?
ํ์ง๋ง ์๋๋ค. ์๋ํ๋ฉด collectAsState()๋ State object ๊ทธ ์์ฒด๊ฐ ์๋๋ผ, State object๋ฅผ ๋ฐํํ๋ Getter ํจ์์ด๊ธฐ ๋๋ฌธ์ด๋ค. State object๊ฐ Get๋๋ ์๋ณธ ๋ฐ์ดํฐ๋ setContent { ... } ๋ฐ์ ์๋ countFlow์ ์ธ์คํด์ค์ ์์นํ๋ค. ๊ทธ๋์ remember { ... }๋ก ๊ฐ์ธ์ง ์๋ ๊ฒ์ด๋ค. ์ดํด๊ฐ ๊ฐ์ง ์๋๋ค๋ฉด State object์ Remembering์ ๋ค๋ฃฌ ์ด ๊ฒ์๊ธ์ ์ฐธ์กฐํ์.
๊ทธ๋ ๋ค๋ฉด ๋ง์ฝ countFlow ํ๋กํผํฐ๊ฐ setContent { ... } ๋ธ๋ก ๋ด๋ถ์ ์์นํ๋ค๋ฉด,
...
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
// Flow ๊ฐ์ฒด
val countFlow = remember {
flow {
for (i in 1..5) {
delay(2000)
emit(i)
}
}
}
Box(
modifier = Modifier.fillMaxSize(),
) {
// collectAsState()
val count = countFlow.collectAsState(initial = 1)
TextExample(
count.value, // State Hoisting (https://kenel.tistory.com/186)
Modifier.align(Alignment.Center)
)
}
}
}
}
@Composable
fun TextExample(...) { ... }
์ด๋ ๊ฒ countFlow ํ๋กํผํฐ์ ๋์
๋๋ Flow ์ธ์คํด์ค๋ฅผ remember { ... }๋ก ๊ฐ์ธ์ฃผ์ด์ผ ํ๋ค. ๊ทธ๋ ์ง ์์ผ๋ฉด ๋ฌดํ ๋ฃจํ์ ๋น ์ง๊ณ ๋ง๋ค. ๋ฌผ๋ก ์ด ๊ฒฝ์ฐ์๋ collectAsState()๋ ๊ทธ์ Getter๋ก์์ ์ญํ ์ ์ํํ ๋ฟ์ด๋ค.
#3-3 ์ค๋ช ์ ๋ํ ์ฆ๊ฑฐ

#3-2์ ์ฝ๋์์ ๋ง์ฝ Flow ์ธ์คํด์ค๋ฅผ remember { ... }๋ก ๊ฐ์ธ์ง ์๋๋ค๋ฉด? ์ด๋ ๊ฒ ๋ฌดํ ๋ฃจํ๊ฐ ๋ฐ์ํ๋ค.

๋ฐ๋ฉด, remember { ... }๋ก ๊ฐ์ธ๋ฉด, Count: 1์ผ๋ก ์์ํด Count: 5๊น์ง ํ์ํ๋ Text๊ฐ ์๋ํ ๋๋ก ์ ํ์๋๋ค.
#4 collectAsState()๋ ๋๊ธฐ ์ฝ๋๋ค
// collect()
public abstract suspend fun collect(
collector: FlowCollector<T>
): Unit
// collectAsState()
@Composable
public fun <T : R, R> Flow<T>.collectAsState(
initial: R,
context: CoroutineContext = EmptyCoroutineContext
): State<R>
collectAsState()์๋ ํ ๊ฐ์ง ๋ ์ง๊ณ ๋์ด๊ฐ์ผ ํ๋ ๊ฒ์ด ์๋ค. ๋ฐ๋ก collect()๊ฐ ๋น๋๊ธฐ ์ฝ๋๋ก์ suspend ํค์๋๊ฐ ๋ถ์ด์๋ ๋ฐ๋ฉด, collectAsState()๋ ๋๊ธฐ ์ฝ๋๊ณ ๋ฐ๋ผ์ suspend ํค์๋๋ ๋ณด์ด์ง ์๋๋ค๋ ์ ์ด๋ค. ๋น์ฐํ๊ฒ ์ง๋ง, collectAsState() ๋์์ ๋น๋๊ธฐ์ ์ด์ง ์์ ์ ์๋ค. ๊ทธ๋ฆฌ๊ณ ์ค์ ๋ก๋ ๋ด๋ถ์ ์ธ ๋์์ ๋น๋๊ธฐ์ ์ผ๋ก ์๋ํ๋ค. ํ์ง๋ง ๊ฒ์ผ๋ก๋ State๋ฅผ ๋ฐํํ๋ ํ๋ฒํ ํจ์์ ๋ชจ์์ด๋ค. ๊ฒฐ๋ก ์ ์ผ๋ก collectAsState()๋ ๋๊ธฐ ์ฝ๋์ธ ์ฒ์ํ๊ณ ์๋ค๋ ๋ง์ด๋ค. ๊ทธ๋์ผ Compose ๋ฐํ์์ด ์ํํ๋ Recomposition์ ๊ธฐ์ (UI ํ์)์ ์ค๋ฉฐ๋ค์ด ํ๋ก๊ทธ๋๋จธ๊ฐ ์ฝ๊ฒ ๋ค๋ฃฐ ์ ์๊ฒ ๋๊ธฐ ๋๋ฌธ์ด๋ค.
์ฌ์ด ์ฌ์ฉ์ฑ ์ธ์๋ collectAsState()๊ฐ ๋๊ธฐ ์ฝ๋์ธ ์ฒ์ ํด์ผํ๋ ์ด์ ๋ ๋ฐ๋ก ์ด๊น๊ฐ์ ์กด์ฌ๋ค. ์ด ์ด๊น๊ฐ์ collectAsState() ๋ด๋ถ์์ flow์ emit()์ ๊ธฐ๋ค๋ฆฌ(๋น๋๊ธฐ ์ฝ๋์ ์ ์๋ '๊ธฐ๋ค๋ฆผ'์ ์กด์ฌ๋ค)๋ ๋์, ์ฌ์ฉ์์๊ฒ ํ์ํ ๊ธฐ๋ณธ๊ฐ์ ์ญํ ์ ์ํํ๋ค. ์ด๋ ๊ฒ ๋ฐ์ดํฐ๊ฐ collectAsState()๋ก ์ ๋ฌ๋๋๋์, UI๊ฐ ๋น ํ๋ฉด์ผ๋ก ํ์๋๋ ๊ฒ์ ์๋ฐฉํ๋ค. ๋ฐ์ดํฐ๊ฐ ์ค๋ฉด ์ค๋ ๋๋ก ๋ฐ์ Recomposition์ ์ํํ๋ฉด ๋๊ณ , ๋ฐ์ดํฐ๊ฐ ์ค๊ธฐ ์ ๊น์ง๋ ์ด๊น๊ฐ์ ์ฆ์ ๋ฐํํ๋ฏ๋ก collectAsState()๋ ๊ฒ์ผ๋ก ๋ด์๋ ๋๊ธฐ ์ฝ๋์ธ ๊ฒ์ด๋ค.
#5 ViewModel์์ Flow ์ฌ์ฉํ๊ธฐ
#5-1 ์ด์ ๊ฒ์๊ธ
[Android] Jetpack Compose - ViewModel์์ State ์ฌ์ฉํ๊ธฐ
#1 ๊ฐ์Jetpack Compose์์ ViewModel์ ์ฌ์ฉํด๋ณธ๋ค. Jetpack Compose๋ฅผ ์ฌ์ฉํ์ง ์๋ ์ ํต์ ์ธ ๋ฐฉ์์์์ ViewModel๊ณผ ํฌ๊ฒ ๋ค๋ฅผ ๊ฒ ์๋ค. Jetpack Compose์ ViewModel์ ๊ตฌํํจ์ผ๋ก์จ State Hoisting ํจํด์ ๊ทน๋ํ์
kenel.tistory.com
์ ๊ฒ์๊ธ์ ๊ธฐ๋ฐํด #3-1์ ์ฝ๋์ ๋๊ฐ์ ๋์์ ํ๋ ์ฝ๋๋ฅผ ViewModel์ ์ด์ฉํด ์์ฑํด๋ณธ๋ค.
#5-2 ๋ชจ๋ ์์ค build.gradle ์์
plugins {
...
}
android {
...
}
dependencies {
...
// ViewModel (Compose)
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.3")
}
#5-3 ์ฝ๋ - MainViewModel
// package com.example.collectasstate
import androidx.lifecycle.ViewModel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.flow
class MainViewModel : ViewModel() {
val numberFlow = flow {
for (i in 1..5) {
delay(1000)
emit(i)
}
}
}
#5-4 ์ฝ๋ - MainActivity
...
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val viewModel = viewModel<MainViewModel>()
Box(
modifier = Modifier.fillMaxSize(),
) {
// collectAsState()
val count = viewModel.countFlow.collectAsState(initial = 1)
TextExample(
count.value, // State Hoisting (https://kenel.tistory.com/186)
Modifier.align(Alignment.Center)
)
}
}
}
}
@Composable
fun TextExample(...) { ... }
#6 ์๋ ํ์ธ
#6-1 Log๋ฅผ ์ถ๊ฐํ ์ฝ๋
...
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Flow ์ ์ธ
val countFlow = flow {
for (i in 1..5) {
delay(2000)
Log.i("interfacer_han", "---\nemit(): $i")
emit(i)
}
}
setContent {
Log.i("interfacer_han", "Recomposition ...")
Box(
modifier = Modifier.fillMaxSize(),
) {
// collectAsState()
val count = countFlow.collectAsState(initial = 1)
Log.i("interfacer_han", "Collected value: ${count.value}")
TextExample(
count.value, // State Hoisting (https://kenel.tistory.com/186)
Modifier.align(Alignment.Center)
)
}
}
}
}
@Composable
fun TextExample(...) { ... }
์๋ ํ์ธ์ ์ํด #3-1์ ์ฝ๋์ Log๋ฅผ ์ถ๊ฐํ๋ค.
#6-2 ์คํฌ๋ฆฐ์ท

์ ์๋ํ๋ค.
#6-3 ๋ก๊ทธ ๋ฉ์์ง
Recomposition ...
Collected value: 1
---
emit(): 1
---
emit(): 2
Recomposition ...
Collected value: 2
---
emit(): 3
Recomposition ...
Collected value: 3
---
emit(): 4
Recomposition ...
Collected value: 4
---
emit(): 5
Recomposition ...
Collected value: 5
flow๊ฐ 1์ emit()ํ์ ๋ Recomposition์ด ์ผ์ด๋์ง ์์ ์ด์ ๋ State.value๊ฐ ์ด๋ฏธ 1์ ๊ฐ์ง๊ณ ์์๊ธฐ ๋๋ฌธ์ด๋ค. ์ฆ, State.value๊ฐ ๋ณํ์ง ์์๊ธฐ ๋๋ฌธ์ด๋ค. Compose ๋ฐํ์์ Stateํ ํ๋กํผํฐ์ ๊ฐ์ ๊ธฐ์ตํ๋ค. ๊ทธ๋์ ๋ฐ์๋ ์๋์ ๋๋ฝ์ด๋ค.
#7 ์์ฝ
collectAsState()๋ ๋๊ธฐ์ฝ๋์ธ ์ฒ์ ํ๋ Getter ํจ์๋ค.
#8 ์์ฑ๋ ์ฑ
android-practice/jetpack-compose/CollectAsState at master ยท Kanmanemone/android-practice
Contribute to Kanmanemone/android-practice development by creating an account on GitHub.
github.com
๊นํ๋ธ์ ์ฌ๋ฆฐ ์๋๋ก์ด๋ ํ๋ก์ ํธ๋ ViewModel์ ํ์ฉํ #5์ ์ฝ๋๋ก ์์ฑํ๋ค.
'๊นจ์ ๊ฐ๋ ๐ > Android' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Android] LiveData - Flow๋ก ๋ง์ด๊ทธ๋ ์ด์ (0) | 2024.08.29 |
---|---|
[Android] Jetpack Compose - StateFlow์ SharedFlow (0) | 2024.08.29 |
[Android] Jetpack Compose - ViewModel์์ State ์ฌ์ฉํ๊ธฐ (0) | 2024.07.23 |
[Android] Jetpack Compose - State Hoisting (0) | 2024.07.23 |
[Android] Jetpack Compose - State Remembering (0) | 2024.07.23 |