#1 ๊ฐ์
#1-1 State์์ StateFlow๋ก ๋ง์ด๊ทธ๋ ์ด์
[Kotlin] Coroutines Flow - StateFlow
#1 ๊ฐ์ StateFlowA SharedFlow that represents a read-only state with a single updatable data value that emits updates to the value to its collectors. A state flow is a hot flow because its active instance exists independently of the presence of collecto
kenel.tistory.com
์ ๊ฒ์๊ธ์ ์ฐธ์กฐํ์ฌ ํ๋ก์ ํธ๋ฅผ ์ ๋ฐ์ดํธํ๋ค.
#1-2 StateFlow ์ ๋ฐ์ดํธ
์ด์ ๊ฒ์๊ธ์ ๋ฌธ์ ์ ์ ํด๊ฒฐํ๊ธฐ ์ํด์, ViewModel์ State ๋ณ์๋ฅผ StateFlow๋ก ๋ณ๊ฒฝ(#1-1)ํ๋ค. ๊ทธ๋ฆฌ๊ณ Repository ํจ์์ ๋ฐํํ์ Flow๋ก ๋ณ๊ฒฝํ ๋ค,
// ๊ตฌ์กฐ๋ฅผ ๋ณด์ด๊ธฐ ์ํ ์ฝ๋๋ก, ์ค์ ์ฝ๋(#2)์๋ ์ธ๋ถ์ ์ผ๋ก ๋ค๋ฆ
repository.getAllItems().collect { itemList ->
_items.value = itemList // Flow๋ก๋ถํฐ ๊ฐ์ ๊ฐฑ์ ๋ฐ๋ StateFlow ํ๋กํผํฐ
}
์ ๊ฐ์ ํํ๋ก ๋ง๋ ๋ค. ์ด๋ ๊ฒ ๊ตฌ์กฐ๋ฅผ ์ง ๋์ผ๋ฉด ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ด๋ค ๋ณ๊ฒฝ์ด ๋ฐ์ํ์ ๋ ๊ทธ ๋ณ๊ฒฝ ์ฌํญ์ด, ๋ฐ์ดํฐ๋ฒ ์ด์ค โ DAO โ Repository โ ViewModel โ View ์์ผ๋ก ์์์ ์ผ๋ก ์ ์ฉ๋๋ค (View๋ ์ด๋ฏธ ViewModel์ ๋ฐ์ดํฐ ๋ฐ์ธ๋ฉ ๋์ด ์์ผ๋ฏ๋ก).
#1-3 ํ๊ณ์ ๊ณผ ๋์ฒ ๋ฐฉ์
๋ณ๊ฒฝ ์ฌํญ์ด ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ View๊น์ง ์ญ ์ด์ด์ง๋ค๋ ๊ฑด 1์ฐจ์ ์ผ๋ก๋ ์ข์ ๋ณด์ธ๋ค. ํ์ง๋ง, ๋ณธ ํ๋ก์ ํธ๋ UI์ Page ๊ฐ๋ ์ด ์ ์ฉ๋์ด ์๋ค. ์ต์ด์ ํน์ ๊ฐฏ์์ ์์๋ฅผ ํ์ํ๊ณ , ์ฌ์ฉ์์ ์คํฌ๋กค์ ๊ฐ์งํ ๋๋ง๋ค ์ถ๊ฐ๋ก ํ์ํ ์์๋ฅผ ๋ถ๋ฌ๋ค์ด๋ ๋ฐฉ์์ด๋ค (๋ฌดํ ์คํฌ๋กค). ์ฆ ์๋์ ํ๋ก ํธ์๋ ๋ก์ง์ ์ ์งํ๋ ค๋ฉด, (#1-2์ ์ฝ๋์ฒ๋ผ) ๋ชจ๋ ์์ดํ ์ ์ ๋ถ ๋ถ๋ฌ์ค๋ ๋ฐฉ์์ ์ฌ์ฉํ ์ ์๋ค๋ ์๊ธฐ๋ค.
Nutri Capture ๋ฐฉํฅ์ฑ - ๊ฐ๋ฐ ์ผ์ ํ (1์ฐจ)
#1 ํ ๊ฐ๋ฐ ํํ์ ๋ํ ๋ฌธ์ ์ #1-1 ๊ณผ์ ์ ์๋ฒฝ์ฃผ์๋ ํ๋ด๊ธฐ ํ๋ก๊ทธ๋๋จธ์ ๋ถ๊ณผํ๋ค. ๋ด๊ฐ ๋ง๋ค ์ฑ ๋ํ ๊ทธ์ ๊ทธ๋ฐ ์ฑ์ผ ๊ฒ์ด๋ค. ์ ์ด๋ ์ฒ์ (์ถ์ํ ์๋๋ก ๋ง๋๋) ์ฑ์ ๋น์ฐํ ๊ทธ๋ด ๊ฒ์ด๋ค,
kenel.tistory.com
๊ทธ๋ผ์๋ (#1-2์ ์ฝ๋์ฒ๋ผ) ํต์งธ๋ก ๋ชจ๋ ์์ดํ ์ ๋ถ๋ฌ์ค๊ธฐ๋ก ํ๋ค. ๊ทธ๊ฒ ์ฝ๊ณ ๋น ๋ฅด๊ธฐ ๋๋ฌธ์ด๋ค. ๊ธฐ๊ป ๊ตฌํํด๋์๋ ๋ฌดํ ์คํฌ๋กค์ ์ ์ ํฌ๊ธฐํ๊ฒ ๋ค. ๊ฐ๋ฐ ์ผ์ ํ๋ฅผ ์ง์ผ์ผํ๊ธฐ ๋๋ฌธ์ด๋ค. ์ ๊ฒ์๊ธ์์, ์ผ๋จ ๋์ํ๋ฉด ๋๊ธฐ๊ธฐ๋ก ์ค์ค๋ก์๊ฒ ์ฝ์ํ๋ค. ๊ทธ๋์ผ ๊ณผ์ ์ ์๋ฒฝ์ฃผ์๊ฐ ์๋๋ผ ๊ฒฐ๊ณผ์ ์๋ฒฝ์ฃผ์๋ฅผ ๋ฌ์ฑํ ์ ์์ผ๋๊น. ๋์ค์ ํ๊บผ๋ฒ์ ๋ฆฌํฉํ ๋งํ๊ฑฐ๋, ์๊ฐ์ด ๋จ์๋ ๋ ์กฐ๊ธ์ฉ ์๊ฐํด๋ณด๊ธฐ๋ก ํ๊ฒ ๋ค. ๊ทธ ํธ์ด ์๊ฐ ๋๋น ๋ ์ข์ ์ฝ๋๊ฐ ๋์ฌ ๊ฒ์ด๋ค (๊ฒฐ๊ณผ์ ์๋ฒฝ์ฃผ์).
์ด๋ฒ ๋ณ๊ฒฝ ์ฌํญ์์ ์ ๊ฑฐํด๋ฒ๋ฆด ์ฝ๋๊ฐ ๊ฝค ๋ ๊ฒ์ด๋ค. ์ด์ฐจํผ ์ด์ ์ฝ๋๋ค์ Git(Hub)์ ๊ธฐ๋ก๋์ด ์์ผ๋ ๋ฌธ์ ๋ ๊ฑด ์๋ค.
#2 ์ฝ๋
#2-1 NutrientViewModel.kt
...
class NutrientViewModel(private val repository: MainRepository) : ViewModel() {
// (1) ํ๋ฉด ํ์์ฉ State
private val _nutrientScreenState = MutableStateFlow(
NutrientScreenState(
dayMeals = SnapshotStateList()
)
)
val nutrientScreenState: StateFlow<NutrientScreenState>
get() = _nutrientScreenState
init {
viewModelScope.launch {
repository.getAllDayMeals().collect { dayMeals ->
_nutrientScreenState.value.dayMeals.apply {
clear()
addAll(dayMeals)
}
}
}
}
// (2) View์์ ๋ฐ์ ์ฒ๋ฆฌํ ์ด๋ฒคํธ
...
// (3) View๋ก๋ถํฐ ๋ฐ์ ์ด๋ฒคํธ ์ฒ๋ฆฌ
fun onEvent(event: NutrientViewModelEvent) {
when (event) {
is NutrientViewModelEvent.InsertMeal -> {
viewModelScope.launch {
repository.insertMeal(event.meal, event.date)
}
}
is NutrientViewModelEvent.DeleteMeal -> {
viewModelScope.launch {
repository.deleteMeal(event.meal)
}
}
is NutrientViewModelEvent.DeleteDayMeal -> {
viewModelScope.launch {
repository.deleteDayMeal(event.dayMeal)
}
}
}
}
}
#1-2์์ ๋งํ ๊ฑธ init { ... } ๋ถ๋ถ์ ๊ตฌํํ๋ค. ๋, onEvent() ๋ถ๋ถ์ด ๋ํญ ๊ฐ์ํ๋์๋ค.
#2-2 MainRepository.kt
// package com.example.nutri_capture_new.db
import kotlinx.coroutines.flow.Flow
import java.time.LocalDate
class MainRepository(private val dao: MainDAO) {
...
fun getAllDayMeals(): Flow<List<DayMealView>> {
return dao.getAllDayMeals()
}
...
}
LiveData๊ฐ ๋ฐํํ์ผ ๋์ ๊ฐ์ ์ด์ ๋ก, ๋ฐํํ์ด Flow์ผ ๋๋ suspend ํค์๋๋ฅผ ๋ถ์ด์ง ์์์ผ ํ๋ค. ๋ฐ๋ผ์ suspend ํค์๋๋ฅผ ์ ๊ฑฐํ๋ค.
#2-3 MainDAO.kt
...
@Dao
interface MainDAO {
...
@Query("""
SELECT * FROM DayMealView
ORDER BY day_date DESC,
meal_time DESC,
meal_id DESC
""")
fun getAllDayMeals(): Flow<List<DayMealView>>
...
}
๋ง์ฐฌ๊ฐ์ง๋ก ๋ฐํํ์ ๋ฐ๊พธ๊ณ , suspend ํค์๋๋ฅผ ์ ๊ฑฐํ๋ค.
#2-4 NutrientViewModelEvent.kt
package com.example.nutri_capture_new.nutrient
import com.example.nutri_capture_new.db.DayMealView
import com.example.nutri_capture_new.db.Meal
import java.time.LocalDate
sealed class NutrientViewModelEvent {
data class InsertMeal(val meal: Meal, val date: LocalDate) : NutrientViewModelEvent()
data class DeleteMeal(val meal: Meal) : NutrientViewModelEvent()
data class DeleteDayMeal(val dayMeal: DayMealView) : NutrientViewModelEvent()
}
State์ ์ด๊น๊ฐ์ ํ ๋นํ๊ธฐ ์ํ ์ด๋ฒคํธ(InitializeState) ๊ทธ๋ฆฌ๊ณ ๋ฌดํ ์คํฌ๋กค ์ด๋ฒคํธ(LoadMoreItemsAfterLastDayMeal)๋ฅผ ์ ๊ฑฐํ๋ค.
#2-5 NutrientScreen.kt
...
@Composable
fun NutrientScreen(
...
) {
LaunchedEffect(key1 = true) {
// ViewModel๋ก๋ถํฐ ๋ฐ์ ์ด๋ฒคํธ ์ฒ๋ฆฌ
viewModel.nutrientScreenEventFlow.collectLatest { event ->
when (event) {
is NutrientScreenEvent.ShowSnackbar -> {
scope.launch {
snackbarHostState.showSnackbar(
message = event.message,
duration = SnackbarDuration.Short
)
}
}
}
}
}
LazyColumn(
...
) {
...
}
}
๋ฌดํ ์คํฌ๋กค ๊ด๋ จ LaunchedEffect() ๋ฑ์ ์ ๊ฑฐํ ๋ชจ์ต์ด๋ค.
#3 ์์ฝ
ViewModel์ด ๋ณด์ ํ๋ Stateํ ํ๋กํผํฐ๋ฅผ StateFlowํ์ผ๋ก ๋ณ๊ฒฝํจ์ผ๋ก์จ, ๋ฐ์ดํฐ ๋ณ๊ฒฝ์ฌํญ(CRUD)์ด DB๋ก๋ถํฐ ViewModel์ ๊ฑฐ์ณ View๊น์ง ์์์ ์ผ๋ก ์ ๋ฌ๋๊ฒ ๋ง๋ค์๋ค.
#4 ์์ฑ๋ ์ฑ
#4-1 ์คํฌ๋ฆฐ์ท

'+' ๋ฒํผ์ '์ ์ก' ๋ฒํผ๊ณผ ์ญํ ์ด ๊ฒน์น์ง๋ง ์ผ๋ถ๋ฌ ์ญ์ ํ์ง ์์๋ค. ์๋ํ๋ฉด ๋ค์ ๊ฒ์๊ธ๋ถํฐ, ChatBar๊ฐ ๋ ์์ธํ ์์ ์ ๋ณด๋ฅผ ๋ด์ INSERT๋ฅผ ์ํํ ์ ์๊ฒ ๋ง๋ค ๊ฒ์ด๊ธฐ ๋๋ฌธ์ด๋ค. ๊ทธ ๊ณผ์ ์์ INSERT๊ฐ ์ ์์ ์ผ๋ก ์๋ํ์ง ์์ ์ ์๋ค. ๋ฐ๋ผ์ '+' ๋ฒํผ์ ํ๋ก์ ํธ๊ฐ ์ข ๋ ์์ ํ๋๋ฉด ๊ทธ ๋ ์ญ์ ํ๋ค.
#4-2 ์ด ๊ฒ์๊ธ ์์ ์ Commit
GitHub - Kanmanemone/nutri-capture-new
Contribute to Kanmanemone/nutri-capture-new development by creating an account on GitHub.
github.com
#4-3 ๋ณธ ํ๋ก์ ํธ์ ๊ฐ์ฅ ์ต์ Commit
GitHub - Kanmanemone/nutri-capture-new
Contribute to Kanmanemone/nutri-capture-new development by creating an account on GitHub.
github.com
'๊ฐ๋ฐ ์ผ์ง ๐ป > Nutri Capture' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
Nutri Capture - ์ฝ๋ ์ ๋ฆฌ (0) | 2025.01.29 |
---|---|
Nutri Capture ํ๋ก ํธ์๋ - ์์ด์ฝ ์ ์ (1์ฐจ) (0) | 2025.01.12 |
Nutri Capture ๋ฐฑ์๋ - ChatBar์ ViewModel ์ฐ๊ฒฐ (0) | 2025.01.02 |
Nutri Capture ๋ฐฉํฅ์ฑ - ๊ฐ๋ฐ ์ผ์ ํ (1์ฐจ) (0) | 2024.12.31 |
Nutri Capture ํ๋ก ํธ์๋ - Dimens์ ํจ๊ป ChatBar ๋ง๋ค๊ธฐ (0) | 2024.12.31 |