LiveData vs Flow
์ํํฐ์ด ๋ถํธ์บ ํ ํ ๋ ์๋๋ก์ด๋ ์คํฐ๋์์ flow๋ฅผ ๊ณต๋ถํ๋๊ฒ ์๊ฐ๋ฌ๋๋ฐ ๋ ธ์ ์๋ง ๋๊ธฐ ์๊น์์ ํฌ์คํ ํ๋ค.
+) ์๋๋ ๋ผ์ด๋ธ๋ฐ์ดํฐ ํญ๋ชฉ์ด ๋จผ์ ์๋๋ฐ, ํ๋ก์ฐ๋ก ์ ํฅํ๋ ์ถ์ธ์ธ๊ฒ ๊ฐ์์ ํ๋ก์ฐ๋ฅผ ๋งจ ์์ผ๋ก ์ฎ๊ฒผ๋ค.
๐Coroutine Flow
- ๋น๋๊ธฐ ์์ ๊ฐ๋ฅํ ๋ฐ์ดํฐ ์คํธ๋ฆผ
- ๋ด๋ณด๋ธ ๊ฐ์ ๋์ผํ ์ ํ์ด์ฌ์ผํ๋ค.
Flow<T>
- suspendํจ์๋ฅผ ์ฌ์ฉํด ๊ฐ์ ๋น๋๊ธฐ์ ์ผ๋ก ์์ฐ ๋ฐ ์ฌ์ฉ
coldStream
: ๊ตฌ๋ ์ ์์๋๋ง ๋ฐ์ดํฐ ์์ฑํ๊ณ ์ฌ๋ฌ ๊ตฌ๋ ์์๊ฒ ๋ ๋ฆฝ์ ์ผ๋ก ๋ฐ์ดํฐ ์ ๋ฌ
suspend function
vs flow
- suspendํจ์๋ ๋จ์ผ ๊ฐ๋ง ๋ฐํ ๊ฐ๋ฅ
- flow๋ ์ฌ๋ฌ๊ฐ์ ์์ฐจ์ ์ผ๋ก ๋ด๋ณด๋ผ ์ ์์! โ ์ฝ๊ฐ stream๋๋!
๋ฐ์ดํฐ ์คํธ๋ฆผ
- ์์ฐ์(Producer) : ์คํธ๋ฆผ์ ์ถ๊ฐ๋๋ ๋ฐ์ดํฐ ์์ฐ โ ์ผ๋ฐ์ ์ผ๋ก ui๋ฐ์ดํฐ ์์ฐ์
- ์ค๊ฐ์(Intermediary)(optional) : ์คํธ๋ฆผ์ด ๋ด๋ณด๋ด๋ ๊ฐ์ด๋ ์คํธ๋ฆผ ์์ฒด๋ฅผ ์์ ๊ฐ๋ฅ
- ์๋น์(Consumer) : ์คํธ๋ฆผ ๊ฐ์ ์ฌ์ฉ โ ์ผ๋ฐ์ ์ผ๋ก ์ฌ์ฉ์ UI
โ ๋ฐ๋์ ๊ฒฝ์ฐ๋ก ์ฌ์ฉ์ UI๊ฐ ์์ฐ์๊ฐ ๋๋ ๊ฒฝ์ฐ๋ ์์
Flow ์ฌ์ฉ
์์ฑ
flow{..}
: FlowBuilder API์ฌ์ฉ, ์ฝ๋ฃจํด ๋ด์์ ์คํemit()
: ์ flow ์์ฑdelay(ms)
: ์ผ์์ ์ง
val latestNews: Flow<List<ArticleHeadline>> = flow {
while(true) {
val latestNews = newsApi.fetchLatestNews()
emit(latestNews) // ์ ๋ฐ์ดํฐ flow๋ฐฉ์ถ
delay(5000) // ์ผ์์ค์ง
}
}
โ flow๋น๋๋ ์ฝ๋ฃจํด ๋ด์์ ์คํ๋์ ๋์ผํ ๋น๋๊ธฐ ์ ์ฝ์ฌํญ
- ์์ฐจ์ ์ธ ํ๋ฆ
- ์์ฐ์๊ฐ ๋ค๋ฅธ CoroutineContext๊ฐ์ emit๋ถ๊ฐโ ์๋ก ์ฝ๋ฃจํด ๋ง๋ค๋ฉด ์๋จ! โ ์ด๊ฒฝ์ฐ
callbackFlow
์ฌ์ฉ๊ฐ๋ฅ
์คํธ๋ฆผ ์์ (์ค๊ฐ์ฐ์ฐ์)
- ์คํธ๋ฆผ ๊ฐ์ ์๋นํ์ง ์๊ณ ๋ฐ์ดํฐ ์คํธ๋ฆผ ์์
-
map
: ๊ฐ ๋ณํflowOf(1, 2, 3) .map { it * 2 } .collect { println(it) } // ์ถ๋ ฅ: 2, 4, 6
-
filter
: ๊ฐ ํํฐ๋งflowOf(1, 2, 3, 4) .filter { it % 2 == 0 } .collect { println(it) } // ์ถ๋ ฅ: 2, 4
-
transform
: map๋ณด๋ค ๋ ์ ์ฐํ ๋ณํ ์ํ๊ฐ๋ฅ, ๊ฐ ๋ฐฉ์ถ, ํํฐ๋ง, ์์ธ๋ฐ์๊ฐ๋ฅflowOf(1, 2, 3) .transform { value -> if (value == 2) { emit(-1) } else { emit(value) } } .collect { println(it) } // ์ถ๋ ฅ: 1, -1, 3
๋ฐ์ดํฐ ์๋น(ํฐ๋ฏธ๋ ์ฐ์ฐ์)
-
collect
: flow์์ ๋ฐ์ํ๋ ๊ฐ ๋ฐ์ดํฐ ์๋น ๋ฐ ๊ฐ ์์์ ๋ํด ์ก์ ์ํflowOf(1, 2, 3) .collect { println(it) } // ์ถ๋ ฅ: 1, 2, 3
-
toList
: ๋ฐฉ์ถ๋๋ ๋ชจ๋ ์์๋ฅผ ๋ฆฌ์คํธ์ ์์งval result: List<Int> = flowOf(1, 2, 3).toList() println(result) // ์ถ๋ ฅ: [1, 2, 3]
-
reduce
: ๋ฐฉ์ถ๋๋ ์์๋ฅผ ์ ์๋ ํจ์๋ฅผ ํตํด ํ๋์ ๊ฐ์ผ๋ก ์ค์, ์์ฐจ์ ์ผ๋ก ๊ฒฐํฉval result: Int = flowOf(1, 2, 3).reduce { acc, value -> acc + value } println(result) // ์ถ๋ ฅ: 6
์์ธ ์ฒ๋ฆฌ
-
catch
: ์์ธ์ฒ๋ฆฌ ๋ธ๋ก ์ ๊ณต, ์๋ฌ ๋ก๊ทธ, ๋์ฒด๊ฐ ๋ฐฉ์ถ ๊ฐ๋ฅflow { emit(1) throw RuntimeException() } .catch { e -> println("Caught exception: $e") } .collect { println(it) }
-
onCompletion
: collect์์ ์ด ์๋ฃ๋์์๋ ํธ์ถ๋๋ ๋ธ๋ก ์ ๊ณต, ์ฑ๊ณต ์ฌ๋ถ์ ์ข ๋ฃ ์์ธ์ ์ ์ ์์flowOf(1, 2, 3) .onCompletion { cause -> if (cause == null) { println("Flow completed successfully.") } else { println("Flow completed with exception: $cause") } } .collect { println(it) }
๋ฐฑ ํ๋ ์ ์ง์
- ํ๋ก์ฐ๊ฐ ๋ฐ์ดํฐ๋ฅผ ๋น ๋ฅด๊ฒ ์์ฐํด๋ ์๋น์๊ฐ ์ค๋น๋ ๋๊น์ง ๋๊ธฐ
- ์ฝ๋ฃจํด suspendํจ์๋ก ์ง์๋จ
context ์ ์ง
- Flow์ฐ์ฐ์ ๋ด์์ ์ฝ๋ฃจํด ์ปจํ์คํธ๋ ์๋์ผ๋ก ์ ํ๋จ
- Flow์ฐ์ฐ์ ์ฒด์ธ์์์ ๊ฐ ๋จ๊ณ๋ ๋์ผํ ์ฝ๋ฃจํด ์ปจํ ์คํธ์์ ์คํ๋จ์ ๋ณด์ฅ
val myFlow = flow {
// GlobalScope.launch { // ์ฌ์ฉ๋ถ๊ฐ
// launch(Dispatchers.IO) { // ์ฌ์ฉ๋ถ๊ฐ
// withContext(CoroutineName("myFlow")) { // ์ฌ์ฉ๋ถ๊ฐ
emit(1) // OK
coroutineScope {
emit(2) // OK -- ๊ฐ์ ์ฝ๋ฃจํด์์ ์คํ๋จ
}
}
StateFlow
& SharedFlow
StateFlow
- ์ฝ๊ฐ ๋ผ์ด๋ธ ๋ฐ์ดํฐ๋ ๋น์ท
- ํญ์ ํ์ฌ ์ํ๊ฐ ๊ฐ์ง๋ Flow
- ์ํ๊ฐ ๋ณ๊ฒฝ ๋ ๋๋ง๋ค ์๋ก์ด ๊ฐ์ด ์๋์ผ๋ก ๋ฐฉ์ถ
- ์ค๋ณต๋ ๊ฐ์ ๋ฐฉ์ถํ์ง ์์
- ํซ์คํธ๋ฆผ์ผ๋ก ๊ด์ฐฐ์๊ฐ ์์ด๋ ๊ฐ์ ์ ์ง!
val mutableStateFlow = MutableStateFlow(0) // ์ด๊ธฐ ์ํ ๊ฐ์ผ๋ก 0 ์ค์
val stateFlow: StateFlow<Int> = mutableStateFlow // StateFlow ํ์
์ผ๋ก ๋
ธ์ถ
// ์ํ ๊ฐ ๋ณ๊ฒฝ
mutableStateFlow.value = 5
// ์ํ ๊ฐ ์ฌ์ฉ
println(stateFlow.value) // ์ถ๋ ฅ: 5
SharedFlow
- ์ฌ๋ฌ ์ปดํฌ๋ํธ๊ฐ ์ด๋ฒคํธ๋ ๋ฐ์ดํฐ ๊ณต์ ํ๋๋ฐ ์ฌ์ฉ๋๋ Flow
- ๋ค์ค์ปฌ๋ ํฐ ์ง์ ( ๋ธ๋ก๋ ์บ์คํ ) โ ์ฌ๋ฌ UI์ปดํฌ๋ํธ๊ฐ ๋์ผํ ๋ฐ์ดํฐ ์คํธ๋ฆผ ๊ตฌ๋ ๊ฐ๋ฅ
- ๋ฐฉ์ถ์ ์ฑ ์ค์ ๊ฐ๋ฅ โ ๋ฒํผํฌ๊ธฐ, ์ด๊ณผ์ ๋์ ๋ฑ
- Replay๊ธฐ๋ฅ โ ์๋ก์ด ์ปฌ๋ ํฐ๊ฐ ๊ตฌ๋ ์์์ ์ต๊ทผ ๋ฐฉ์ถ๋ N๊ฐ์ ๊ฐ์ ๋ค์ ๋ฐ์ ์ ์์
- ํซ ์คํธ๋ฆผ : ๊ด์ฐฐ์ ์์ด๋ ํ์ฑ์ํ ์ ์ง!
val sharedFlow = MutableSharedFlow<Int>()
// ๋ค๋ฅธ ์ฝ๋ฃจํด์์ ๋ฐ์ดํฐ๋ฅผ ๋ฐฉ์ถ
launch {
sharedFlow.emit(1)
sharedFlow.emit(2)
sharedFlow.emit(3)
}
// ์ฌ๋ฌ ์ปฌ๋ ํฐ๊ฐ ๋์์ ๋ฐ์ดํฐ๋ฅผ ์์
launch {
sharedFlow.collect { value ->
println("Collector 1 received: $value")
}
}
launch {
sharedFlow.collect { value ->
println("Collector 2 received: $value")
}
}
๐ณย LiveData
- ๊ด์ฐฐ ๊ฐ๋ฅํ ๋ฐ์ดํฐ ํ๋ ํด๋์ค
์ฅ์
- UI,๋ฐ์ดํฐ์ํ์ ์ผ์น ๋ณด์ฅ
- ๋ฉ๋ชจ๋ฆฌ ๋์ ์์ โ ์๋ช ์ฃผ๊ธฐ ๋๋๋ฉด ์๋์ญ์
- ์๋ช ์ฃผ๊ธฐ ์๋์ผ๋ก ์ฒ๋ฆฌ
- ์ฑ๊ธํค ํจํด์ ์ฌ์ฉํ๋ LiveData๊ฐ์ฒด๋ฅผ ํ์ฅํด ์์คํ ์๋น์ค ๋ํ ๊ฐ๋ฅ
์ฌ์ฉ
- LiveData์ธ์คํด์ค ์์ฑ (์ผ๋ฐ์ ์ผ๋ก ๋ทฐ๋ชจ๋ธ)
- ๊ทธ๋ฅ ์ ์ธํ๋ฉด ์ฒ์์๋ LiveData๊ฐ์ฒด ๋ฐ์ดํฐ ์ค์ ์๋จ
onChanged()
๋ฉ์๋ ์ ์ํ๋ Observer๊ฐ์ฒด ๋ง๋ฆobserve()
๋ฉ์๋๋ก LiveData์ Observer๊ฐ์ฒด ์ฐ๊ฒฐonCreate()
๊ฐ ์ ์ , ํ๋๊ทธ๋จผํธ๋onViewCreated
onResume()
๋ฉ์๋์์ ์ค๋ณตํธ์ถ ๋ฐฉ์ง
- LiveData๊ฐ์ฒด ๊ฐ ์ ๋ฐ์ดํธ์ ํ์ฑ์ํ์ธ ๋ชจ๋ ๊ด์ฐฐ์๊ฐ ํธ๋ฆฌ๊ฑฐ
๊ฐ์ฒด ์ ๋ฐ์ดํธ
- ์ ์ฅ๋ ๋ฐ์ดํฐ ์ ๋ฐ์ดํธํ๋ ๋ฉ์๋ ์์
- MutableLiveDta๋ setValue, postValue๋ฉ์๋๋ฅผ public์ผ๋ก ๋ ธ์ถํด์ ์ ์ฅ๋ ๊ฐ ์์
- ๋ทฐ๋ชจ๋ธ์ ๋ณ๊ฒฝ ๋ถ๊ฐ๋ฅํ LiveData ๊ฐ์ฒด๋ง ๊ด์ฐฐ์์๊ฒ ๋ ธ์ถ โ backing property!
๋ทฐ๋ชจ๋ธ์์ ๋ผ์ด๋ธ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉํ๋ ์ด์
- ๋ทฐ๋ชจ๋ธ์ ์ํ๋ฅผ ๋ณด์
- UI์ปจํธ๋กค๋ฌ๋ ๋ฐ์ดํฐ ํ์๋ฅผ ๋ด๋นํ๊ณ ์ํ๋ ๋ณด์ ํ์ง ์์
- ์๋ช ์ฃผ๊ธฐ๊ฐ ๋ ๊ธธ๋ค.
Lifecycle Aware Component
- ์กํฐ๋นํฐ, ํ๋๊ทธ๋จผํธ ๋ฑ ์๋ช ์ฃผ๊ธฐ ๊ณ ๋ ค
- ํ์ฑ ์ํ ๊ด์ฐฐ์์๊ฒ๋ง ์ ๋ฐ์ดํธ ์๋ฆผ โ STARTED~RESUMED
- ๋นํ์ฑ ์ํ์์ ํ์ฑ ์ํ๋ก ๋ณ๊ฒฝ๋ ๋๋ ์ ๋ฐ์ดํธ ๋ฐ์ โ ์ด๊ฒฝ์ฐ ๊ฐ์ฅ ์ต์ ๋ฐ์ดํฐ
๊ด์ฐฐ์(Observer)
- LifecycleOwner์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ ๊ฐ์ฒด์ ํ์ด๋ง๋ ๊ด์ฐฐ์
observerForever
๋ฉ์๋๋ฅผ ํตํด LifecycleOwner๊ฐ์ฒด ์๋ ๊ด์ฐฐ์๋ฅผ ๋ฑ๋ก ๊ฐ๋ฅ
์ํคํ ์ฒ๋ฅผ ์ํ LiveData์์น
- ์กํฐ๋นํฐ, ํ๋๊ทธ๋จผํธ๋ ์ํ๋ฅผ ๋ณด์ ํ์ง ์๊ณ ๋ฐ์ดํฐ๋ฅผ ํ์ํ๋ ์ญํ ๋ง ํ๋ฏ๋ก LiveData์ธ์คํด์ค๋ฅผ ๋ณด์ ํ๋ฉด ์๋จ
- LiveData๋ ๋น๋๊ธฐ ๋ฐ์ดํฐ ์คํธ๋ฆผ์ ์ฒ๋ฆฌํ๋๋ก ์ค๊ณ๋์ง ์์์ผ๋ฏ๋ก ๋ฐ์ดํฐ ๋ ์ด์ด์์ LiveData์ฌ์ฉ์๋จ.
โ ๋ฐ์ดํฐ ์คํธ๋ฆผ ์ฌ์ฉ : Kotlin Flow + asLiveData๋ก ๋ทฐ๋ชจ๋ธ์ LiveData๋ก ๋ณํ
โ(์๋ฐ) RxJava๋ Executor๋ฅผ ์ฌ์ฉ
LiveData & ROOM
- DAO์ผ๋ถ๋ก ์์ฑ
- DB์ ๋ฐ์ดํธ์ Room์ด LiveData์ ๋ฐ์ดํธ ํด์ฃผ๋ ์ฝ๋ ์์ฑ
- ์์ฑ๋ ์ฝ๋๋ ํ์์ ๋ฐฑ๊ทธ๋ผ์ด๋์์ ๋น๋๊ธฐ์ ์ผ๋ก ์ฟผ๋ฆฌ ์คํ
- UI ๋ฐ์ดํฐ์ DB๋ฐ์ดํฐ์ ๋๊ธฐํ์ ์ ์ฉ
LiveData & Coroutine
- ์ฝ๋ฃจํด ์ง์ ๊ฐ๋ฅ
LiveData๋ด๋ถ ํจ์ (์์ ํ override๊ฐ๋ฅ)
onActive()
: ํ์ฑ ์ํ์ ๊ด์ฐฐ์๊ฐ ์์๋ ํธ์ถonInactive()
: ํ์ฑ์ํ์ ๊ด์ฐฐ์๊ฐ ์์๋ ํธ์ถsetValue(T)
: LiveData์ธ์คํด์ค ๊ฐ์ ์ ๋ฐ์ดํธ ํ๊ณ ๋ชจ๋ ํ์ฑ ์ํ ๊ด์ฐฐ์์๊ฒ ๋ณ๊ฒฝ์ฌํญ ์๋ฆผ
๋ผ์ด๋ธ๋ฐ์ดํฐ๋ก ์กํฐ๋นํฐ, ํ๋๊ทธ๋จผํธ, ์๋น์ค ๊ฐ ๊ฐ์ฒด๊ณต์
- ์ฑ๊ธํค ํจํด ์ ์ฉ
class StockLiveData(symbol: String) : LiveData<BigDecimal>() {
private val stockManager: StockManager = StockManager(symbol)
private val listener = { price: BigDecimal ->
value = price
}
override fun onActive() {
stockManager.requestPriceUpdates(listener)
}
override fun onInactive() {
stockManager.removeUpdates(listener)
}
companion object {
private lateinit var sInstance: StockLiveData
@MainThread
fun get(symbol: String): StockLiveData {
sInstance = if (::sInstance.isInitialized) sInstance else StockLiveData(symbol)
return sInstance
}
}
}
- ์ฑ ์ ์ญ์์ ํธ๋ฆฌ๊ฑฐ ๋ฐ์ ์ ์์
LiveData ๋ณํ : Transformations
Transforamtions.map()
: ์ ์ฅ๋ ๊ฐ์ ํจ์๋ฅผ ์ ์ฉํ๊ณ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ๋ก ๋ฐํTransformation.switchMap()
: map๊ณผ ๋น์ทํ๋ฐ ๋ฐํ๊ฐ์ด LiveData๊ฐ์ฒด
LiveData
vs StateFlow
๊ณตํต์
- ๊ด์ฐฐ๊ฐ๋ฅํ ๋ฐ์ดํฐ ํ๋ ํด๋์ค
- ์ฑ ์ํคํ ์ฒ์์ ์ฌ์ฉ์ ๋น์ทํ ํจํด๋ฐ๋ฆ
์ฐจ์ด์
StateFlow
- ์ด๊ธฐ ์ํ๋ฅผ ์์ฑ์์ ์ ๋ฌํด์ผํจ
- ์๋ช
์ฃผ๊ธฐ์ ๋ฐ๋ฅธ ๊ณ ๋ ค๋ฅผ ์ํด์
Lifecycle.repeatOnLifecycle
์ฌ์ฉํด์ผํจ(๊ธฐ๋ณธ์ ์ผ๋ก ์ง์์ํจ)
LiveData
- ์ด๊ธฐ ์ํ๋ฅผ ์ง์ ํ์ง ์์ ์ ์์
- ์๋์ผ๋ก ์๋ช ์ฃผ๊ธฐ๋ฅผ ๊ณ ๋ ค
๐์ฐธ๊ณ ์๋ฃ
LiveData ๊ฐ์ |ย Android ๊ฐ๋ฐ์ ย |ย Android Developers
๋๊ธ๋จ๊ธฐ๊ธฐ