#1 ๊ฐ์
์ด์ ๊ฒ์๊ธ์์ ์ฑํ UI ๋์ ์ ์ํด ๊ฐ์ ํ ์ด๋ธ DayMealView๋ฅผ ๋ง๋ค์๋ค. ๋ฐฑ์๋ ์์ ์ด ๋๋ฌ์ผ๋ฏ๋ก, Day ํ ์ด๋ธ ๋ฐ Meal ํ ์ด๋ธ์ ์ฐธ์กฐํ๋ ํ๋ก ํธ๊ฐ ์ด์ ๋ DayMealView ํ ์ด๋ธ์ ์ฐธ์กฐํ๋๋ก ๋ง๋ค์ด์ผ ํ๋ค.
#2 ์ฝ๋ - ๋ฐฑ์๋ ๊นจ์ ๋ณ๊ฒฝ ์ฌํญ
#2-1 DAO์์ getAllDayMeals(limit: Int) ์ ์ธ
...
@Dao
interface MainDAO {
...
@Query("SELECT * FROM DayMealView LIMIT :limit")
suspend fun getAllDayMeals(limit: Int): List<DayMealView>
...
}
์กด์ฌํ๋ ๋ชจ๋ ๋ ์ฝ๋๋ฅผ ๊ฐ์ ธ์ค๋ getAllDayMeals()์ ๋ฉ์๋ ์ค๋ฒ๋ก๋ฉ์ด๋ค. getAllDayMeals() ๋ฐ๋ก ์๋์ ์ ์ธํ๋ค.
#2-2 Repository์์๋ ์ ์ธ
...
class MainRepository(private val dao: MainDAO) {
...
suspend fun getAllDayMeals(limit: Int): List<DayMealView> {
return dao.getAllDayMeals(limit)
}
...
}
DAO์์ ์ ์ธํ ํจ์๋ฅผ ์ฐธ์กฐํ๋ ํจ์๋ค.
#3 ์ฝ๋ - View์์ ํ์ํ State ๋ณ๊ฒฝ
#3-1 NutrientScreenState
// package com.example.nutri_capture_new.nutrient
import androidx.compose.runtime.snapshots.SnapshotStateList
import com.example.nutri_capture_new.db.DayMealView
data class NutrientScreenState(
val dayMeals: SnapshotStateList<DayMealView>
)
๊ฐ์ ํ ์ด๋ธ DayMealView์ ๊ฐ ์์ดํ ์ด ๋ด๊ธด ๋ฆฌ์คํธ๋ง์ ์ ์ธํ๋ค.
#3-2 ViewModel์ ๋ด๋ถ ํ๋กํผํฐ
...
class NutrientViewModel(private val repository: MainRepository) : ViewModel() {
// (1) ํ๋ฉด ํ์์ฉ State
private val _nutrientScreenState = mutableStateOf(
NutrientScreenState(
dayMeals = SnapshotStateList()
)
)
val nutrientScreenState: State<NutrientScreenState>
get() = _nutrientScreenState
...
}
#3-1 ๋ณ๊ฒฝ ์ฌํญ์ ๋ง์ถฐ ViewModel๋ฅผ ์์ ํ๋ค.
#3-3 InitializeState ์ด๋ฒคํธ ๋ณ๊ฒฝ
...
class NutrientViewModel(private val repository: MainRepository) : ViewModel() {
...
// (4) View๋ก๋ถํฐ ๋ฐ์ ์ด๋ฒคํธ ์ฒ๋ฆฌ
fun onEvent(event: NutrientViewModelEvent) {
when (event) {
is NutrientViewModelEvent.InitializeState -> {
viewModelScope.launch {
_nutrientScreenState.value.dayMeals.apply {
clear()
addAll(repository.getAllDayMeals(10))
}
}
_isInitialized.value = true
}
...
}
}
// (5) DayMealView ์๋ ํ์ธ์ฉ ๋ก๊ทธ
...
}
ViewModel.onEvent()์ ๋ถ๊ธฐ ์ค ํ๋์ธ NutrientViewModelEvent.InitializeState๋ฅผ ๋ณ๊ฒฝํ๋ค.
#3-4 LoadMoreItemsAfterLastDate ์ด๋ฒคํธ ์ด๋ฆ ๋ฐ ๋์ ๋ณ๊ฒฝ
...
class NutrientViewModel(private val repository: MainRepository) : ViewModel() {
...
// (4) View๋ก๋ถํฐ ๋ฐ์ ์ด๋ฒคํธ ์ฒ๋ฆฌ
fun onEvent(event: NutrientViewModelEvent) {
when (event) {
...
is NutrientViewModelEvent.LoadMoreItemsAfterLastDayMeal -> {
viewModelScope.launch {
val lastDayMeal = _nutrientScreenState.value.dayMeals.last()
_nutrientScreenState.value.dayMeals.addAll(
repository.getNextDayMealsAfter(lastDayMeal, 10)
)
}
}
...
}
}
// (5) DayMealView ์๋ ํ์ธ์ฉ ๋ก๊ทธ
...
}
LoadMoreItemsAfterLastDate์ ์ด๋ฆ์ LoadMoreItemsAfterLastDayMeal๋ก ๋ฐ๊พผ๋ค (๋น์ฐํ๊ฒ ์ง๋ง ๋ณ์๋ช ํด๋ฆญ ํ Shift + F6 ๋๋ฌ์ ํ๋ก์ ํธ ๋ด์ ๋ชจ๋ LoadMoreItemsAfterLastDate๋ฅผ ์ผ๊ด์ ์ผ๋ก ๋ณ๊ฒฝํ๋ค). ๋, ์ด๋ฒคํธ ๋ด์ฉ๋ ์์ ๊ฐ์ด ๋ณ๊ฒฝํ๋ค.
#3-5 InsertMeal ์ด๋ฒคํธ ๋ณ๊ฒฝ
...
class NutrientViewModel(private val repository: MainRepository) : ViewModel() {
...
// (4) View๋ก๋ถํฐ ๋ฐ์ ์ด๋ฒคํธ ์ฒ๋ฆฌ
fun onEvent(event: NutrientViewModelEvent) {
when (event) {
...
is NutrientViewModelEvent.InsertMeal -> {
viewModelScope.launch {
var insertedMealId = -1L
insertedMealId = repository.insertMeal(event.meal, event.date)
if (insertedMealId != -1L) {
val insertedDayMeal = repository.getDayMeal(insertedMealId)
val index =
findIndexToInsert(_nutrientScreenState.value.dayMeals, insertedDayMeal)
_nutrientScreenState.value.dayMeals.add(index, insertedDayMeal)
}
}
}
...
}
}
// (5) DayMealView ์๋ ํ์ธ์ฉ ๋ก๊ทธ
...
}
private fun findIndexToInsert(list: SnapshotStateList<DayMealView>, newItem: DayMealView): Int {
for(i: Int in list.indices) {
if(newItem < list[i]) {
return i
}
}
// ์ฝ์
ํ ์์น๊ฐ ์๋ค๋ฉด ๋งจ ๋(list.size)์ ์ฝ์
return list.size
}
์๋ if (insertedMealId != -0L) { ... } ์๋ค. ์ฝ์ ์คํจ ์ ๋ฐํ๋๋ ๊ฐ์ 0์ด ์๋๋ผ -1๋ฏ๋ก ์์ ๊ฐ์ด ์์ ํ๋ค.
findIndexToInsert๋ ๊ต์ฅํ ๋จ์ํ ์ฝ์ ์๊ณ ๋ฆฌ์ฆ์ด๋ค. ๋์ค์ ๋ฆฌ์คํธ์ ๊ธธ์ด๊ฐ ๊ธธ์ด์ง๋ฉด ๋ง์ ์์์ ๋จน๊ฒ๋ ๊ฒ์ด๋ค. ์ง๊ธ์ ๋์ด๊ฐ๊ฒ ์ง๋ง ์ถํ ์ด์ง ํ์์ ์์ฉํ ๋ฐฉ์์ผ๋ก ๋ฆฌํฉํ ๋งํ๋ค.
๋, DayMealView ๊ฐ ๋์๋น๊ต๋ฅผ ํ๊ณ ์๋๋ฐ ์๋๋ผ๋ฉด ๋ถ๊ฐ๋ฅํ ์ฐ์ฐ์ด๋ค. ์ด๋ฅผ ๊ฐ๋ฅ์ผํ๊ธฐ ์ํด์ DayMealView๋ฅผ ์๋์ ๊ฐ์ด ์ ๋ฐ์ดํธํ๋ค.
#3-6 DayMealView์ ์ฐ์ฐ์ ์ค๋ฒ๋ก๋ฉ
// package com.example.nutri_capture_new.db
...
@DatabaseView("""
...
""")
data class DayMealView(
...
) {
operator fun compareTo(otherDayMealView: DayMealView): Int {
return compareValuesBy(this, otherDayMealView,
{ it.date }, // (1์ฐจ) date์ ๋ํด ์ค๋ฆ์ฐจ์์ผ๋ก ๋น๊ต (date๊ฐ ํฐ ์ชฝ์ด ๋น๊ต ์ฐ์)
{ it.time }, // (2์ฐจ) time์ ๋ํด ์ค๋ฆ์ฐจ์์ผ๋ก ๋น๊ต (time์ด ํฐ ์ชฝ์ด ๋น๊ต ์ฐ์)
{ it.mealId } // (3์ฐจ) mealId์ ๋ํด ์ค๋ฆ์ฐจ์์ผ๋ก ๋น๊ต (mealId๊ฐ ํฐ ์ชฝ์ด ๋น๊ต ์ฐ์)
) * -1 // ๋ถํธ๋ฅผ ๋ฐ์ ์ํค๋ฉด, ๋ด๋ฆผ์ฐจ์์ผ๋ก ๋น๊ตํ ๊ฒ๊ณผ ๋์ผํ ๊ฒฐ๊ณผ๊ฐ ๋์ด
}
}
์ฐธ๊ณ : ์ฐ์ฐ์ ์ค๋ฒ๋ก๋ฉ (Operator overloading)
#3-7 DeleteMeal ์ด๋ฒคํธ ๋ณ๊ฒฝ
...
class NutrientViewModel(private val repository: MainRepository) : ViewModel() {
...
// (4) View๋ก๋ถํฐ ๋ฐ์ ์ด๋ฒคํธ ์ฒ๋ฆฌ
fun onEvent(event: NutrientViewModelEvent) {
when (event) {
...
is NutrientViewModelEvent.DeleteMeal -> {
viewModelScope.launch {
var deletedRowCount = 0
deletedRowCount = repository.deleteMeal(event.meal)
if (deletedRowCount == 1) {
val deletedMealId = event.meal.mealId
_nutrientScreenState.value.dayMeals.removeIf { it.mealId == deletedMealId }
}
}
}
}
}
// (5) DayMealView ์๋ ํ์ธ์ฉ ๋ก๊ทธ
...
}
...
#3-8 ๋ฏธ์ฌ์ฉ ์ด๋ฒคํธ ์ญ์
// package com.example.nutri_capture_new.nutrient
import com.example.nutri_capture_new.db.Meal
import java.time.LocalDate
sealed class NutrientViewModelEvent {
data object InitializeState : NutrientViewModelEvent()
data object LoadMoreItemsAfterLastDayMeal : NutrientViewModelEvent()
data class InsertMeal(val meal: Meal, val date: LocalDate) : NutrientViewModelEvent()
data class DeleteMeal(val meal: Meal, val date: LocalDate) : NutrientViewModelEvent()
}
LoadMoreItemsBeforeFirstDate์ GetMealsByDate ๋ถ๊ธฐ๋ฅผ onEvent() ๋ถ๊ธฐ๋ฌธ์์ ์ญ์ ํ๋ค. ๊ทธ๋ฆฌ๊ณ , NutrientViewModelEvent์์๋ ์ ๊ฑฐํ๋ค.
#4 ์ฝ๋ - View (NutrientScreen)
#4-1 INSERT๋ฅผ ์ํํ๋ ์์ ํจ์
...
@Composable
fun NutrientScreen(
...
) {
LaunchedEffect(key1 = true) {
...
}
LaunchedEffect(key1 = true) {
repeat(5) {
viewModel.onEvent(
NutrientViewModelEvent.InsertMeal(
meal = Meal(
time = LocalTime.now(),
name = "test",
nutritionInfo = NutritionInfo()
),
date = LocalDate.now()
)
)
}
}
LaunchedEffect(key1 = viewModel.isInitialized.value) {
...
}
LazyColumn(
...
) {
...
}
}
#4-3์์, ์๋๋ ์กด์ฌํ๋ Meal ์ถ๊ฐ ๋ฒํผ์ด ์์ด์ง๋ฏ๋ก ์ด๋ ๊ฒ๋ผ๋ ๊ตฌํํ๋ค. ์ฑ์ด ์คํ๋๋ฉด 1์ด ๊ฐ๊ฒฉ์ผ๋ก Meal์ด ํ๋์ฉ INSERT๋ ๊ฒ์ด๋ค. Meal ์ถ๊ฐ ๋ฒํผ์ ๋ค์ ๋ง๋๋ ๊ฒ๊ณผ ๋์์ ์ญ์ ํ ํจ์๋ค.
#4-2 ์ญ๋ฐฉํฅ ๋ฌดํ ์คํฌ๋กค ์ญ์
...
@Composable
fun NutrientScreen(
...
) {
LaunchedEffect(key1 = true) {
...
}
LaunchedEffect(key1 = true) {
...
}
LaunchedEffect(key1 = viewModel.isInitialized.value) {
if (viewModel.isInitialized.value) {
// ๋ฌดํ ์คํฌ๋กค
snapshotFlow { listState.layoutInfo.visibleItemsInfo }.collect { visibleItemsInfo ->
val totalMaxIndex = listState.layoutInfo.totalItemsCount - 1
val firstVisibleItemIndex = listState.firstVisibleItemIndex
val visibleItemCount = visibleItemsInfo.size
if (totalMaxIndex <= firstVisibleItemIndex + visibleItemCount) {
viewModel.onEvent(NutrientViewModelEvent.LoadMoreItemsAfterLastDayMeal)
}
}
}
}
LazyColumn(
...
) {
...
}
}
์ฑํ UI๋ ์๋ฐฉํฅ ๋ฌดํ ์คํฌ๋กค์ด ์๋ค. ์๋ ์๋ ์ญ๋ฐฉํฅ ๋ฌดํ ์คํฌ๋กค ์ฝ๋๋ฅผ ์ ๊ฑฐํ๊ณ ์๋ฐฉํฅ ๋ฌดํ ์คํฌ๋กค ์ฝ๋๋ง ๋จ๊ธด๋ค. ์ญ๋ฐฉํฅ ๋ฌดํ ์คํฌ๋กค์ด ํน์ ๋์ค์๋ผ๋ ํ์ํ๋ฉด ๋ค์ ๊ตฌํํ๊ฒ ๋ค.
#4-3 LazyColumn ๋ณ๊ฒฝ
...
@Composable
fun NutrientScreen(
...
) {
LaunchedEffect(key1 = true) {
...
}
LaunchedEffect(key1 = true) {
...
}
LaunchedEffect(key1 = viewModel.isInitialized.value) {
...
}
LazyColumn(
...
reverseLayout = true
) {
val dayMeals = viewModel.nutrientScreenState.value.dayMeals
itemsIndexed(dayMeals) { index, dayMeal ->
Card(
modifier = Modifier
.fillMaxWidth()
.padding(
start = 8.dp,
top = if (index == dayMeals.lastIndex) 8.dp else 0.dp,
end = 8.dp,
bottom = 8.dp
),
elevation = CardDefaults.cardElevation(8.dp)
) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(8.dp)
) {
Text(
text = DateFormatter.formatDateForNutrientScreen(dayMeal.date) + " " + dayMeal.time,
modifier = Modifier.fillMaxWidth(),
fontSize = 15.sp,
textAlign = TextAlign.End
)
Text(
text = "mealId: ${dayMeal.mealId}, index: $index",
modifier = Modifier.fillMaxWidth(),
fontSize = 30.sp,
textAlign = TextAlign.Center
)
}
}
}
}
}
LazyColumn์ reverseLayout ํ๋กํผํฐ์ true๋ฅผ ๋์ ํ๋ค. ์ด์ LazyColumn์ ์์ดํ ์ ์์๋ ๋ค์งํ ๊ฒ์ด๊ณ , ์๋ฐฉํฅ ๋ฌดํ์คํฌ๋กค์ ๋ฐฉํฅ๋ (์ → ์๋)์์ (์๋ → ์)๋ก ๋ค์งํ ๊ฒ์ด๋ค.
#5 ์์ฝ
๊ฐ์ ํ ์ด๋ธ DayMealView๋ฅผ ์ฐธ์กฐํ๋๋ก ViewModel ๋ฐ View๋ฅผ ๋ณ๊ฒฝํ๋ค.
#6 ์์ฑ๋ ์ฑ
#6-1 ๋ฌธ์ ์
์ฑ์ ์คํ์ํค๋ฉด ๋ฐ๋ก ํ๊ธด๋ค. ์คํ์ด ์๋๋ ๋ฒ์ ์ Commit์ผ๋ก ๋ฑ๋กํ๋ ๊ฒ ์ฝ๊ฐ ๊ป๋๋ฝ๋ค. ํ์ง๋ง, ๋ณธ ๊ฒ์๊ธ์ด ๊ณผ๋ํ๊ฒ ๊ธธ์ด์ง๊ธฐ์ ์ด ์๋ฌ์ ํด๊ฒฐ ๊ณผ์ ์ ๋ค์ ๊ฒ์๊ธ์์ ๋ค๋ฃจ๊ฒ ๋ค.
#6-2 ์ด ๊ฒ์๊ธ ์์ ์ Commit
GitHub - Kanmanemone/nutri-capture-new
Contribute to Kanmanemone/nutri-capture-new development by creating an account on GitHub.
github.com
#6-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) | 2024.11.16 |
---|---|
Nutri Capture ๋ฐฑ์๋ - Room ๋ฌด๊ฒฐ์ฑ ๋ณด์ (1) | 2024.11.15 |
Nutri Capture ๋ฐฑ์๋ - Room ๊ฐ์ ํ ์ด๋ธ์ ๋ ์ฝ๋๋ฅผ ํ์ํ ๋งํผ๋ง ๊ฐ์ ธ์ค๊ธฐ (0) | 2024.11.12 |
Nutri Capture ๋ฐฑ์๋ - Room ๊ฐ์ ํ ์ด๋ธ ์ ์ธ (0) | 2024.11.10 |
Nutri Capture ๋ฐฉํฅ์ฑ - ์ฑํ UI (1) | 2024.11.08 |