#1 ๊ฐ์
์ด์ ๊ฒ์๊ธ์ Commit์ ์ฑ ์คํ์ด ์๋๋ ๋ฒ๊ทธ๊ฐ ์๋ค. ํด๋น ๋ฒ๊ทธ๋ฅผ ๋ฐ์์ํจ ๊ทผ๋ณธ์ ์ธ ๋ถ๋ถ์ ์ฐพ์๊ฐ๋ค. ํนํ, ์ธ๋ฒ์งธ ์๋ฌ๋ Room์ ๋ฌด๊ฒฐ์ฑ๊ณผ ๊ด๋ จ๋ ์๋ฌ๋ก ํด๊ฒฐ๊น์ง ๊ฝค ์๊ฐ์ด ์์๋์ผ๋ฉฐ, ๋์์ Room ๊ตฌํ์ ์์ด DAO๋ฅผ ๊ฒฝ์ํ ์์ฑํด์๋ ์๋๋ค๋ ๊ตํ์ ๋ด๊ฒ ์ฃผ์๋ค.
#2 ์ฝ๋ - ์ฒซ๋ฒ์งธ ์๋ฌ
#2-1 NoSuchElementException
FATAL EXCEPTION: main (Ask Gemini)
Process: com.example.nutri_capture_new, PID: 7679
java.util.NoSuchElementException: List is empty.
at kotlin.collections.CollectionsKt___CollectionsKt.last(_Collections.kt:418)
at com.example.nutri_capture_new.nutrient.NutrientViewModel$onEvent$2.invokeSuspend(NutrientViewModel.kt:51)
...
#2-2 ๋ฐ์ ์์ธ
// in NutrientViewModel.kt
is NutrientViewModelEvent.LoadMoreItemsAfterLastDayMeal -> {
viewModelScope.launch {
val lastDayMeal = _nutrientScreenState.value.dayMeals.last()
_nutrientScreenState.value.dayMeals.addAll(
repository.getNextDayMealsAfter(lastDayMeal, 10)
)
}
}
NutrientViewModel.onEvent() ์ When(NutrientViewModelEvent)์ ํ ๋ถ๊ธฐ๋ฌธ์์ ๋ฐ์ํ ์๋ฌ๋ค. List๊ฐ ๋น์ด์๋ ๊ฒฝ์ฐ, List.last()๋ฅผ ์ ์ฉํ ์ ์์ด์ ๋ฐ์ํ๋ค.
#2-3 ๋์ฒ
// in NutrientViewModel.kt
is NutrientViewModelEvent.LoadMoreItemsAfterLastDayMeal -> {
viewModelScope.launch {
if(_nutrientScreenState.value.dayMeals.isNotEmpty()) {
val lastDayMeal = _nutrientScreenState.value.dayMeals.last()
_nutrientScreenState.value.dayMeals.addAll(
repository.getNextDayMealsAfter(lastDayMeal, 10)
)
}
}
}
if๋ฌธ์ ํ๋ ์ถ๊ฐํ๋ค.
#3 ์ฝ๋ - ๋๋ฒ์งธ ์๋ฌ
#3-1 NullPointerException
FATAL EXCEPTION: main (Ask Gemini)
Process: com.example.nutri_capture_new, PID: 7839
java.lang.NullPointerException: Attempt to invoke virtual method 'java.time.LocalDate com.example.nutri_capture_new.db.DayMealView.getDate()' on a null object reference
at com.example.nutri_capture_new.db.MainRepository.getNextDayMealsAfter(MainRepository.kt:43)
...
#3-2 ๋ฐ์ ์์ธ
// in MainRepository.kt
suspend fun getNextDayMealsAfter(lastDayMeal: DayMealView, limit: Int): List<DayMealView> {
return dao.getNextDayMealsAfter(
lastDayMeal.date, lastDayMeal.time, lastDayMeal.mealId, limit
)
}
์์ lastDayMeal.date์ null์ด ๋ด๊ฒจ ์์ด์ ๋ฐ์ํ๋ค. ํ์ง๋ง, ๋ด ์๋๋๋ก๋ผ๋ฉด lastDayMeal์๋ null๊ฐ์ด ๋ค์ด๊ฐ ์๊ฐ ์๋ค. ์์ธ์ ์ด์ด์๊ฒ๋,
// in NutrientViewModel.kt
fun log() {
viewModelScope.launch {
Log.i("interfacer_han, getAllDayMeals()", repository.getAllDayMeals().toString())
Log.i("interfacer_han, getNextDayMealsAfter()",
repository.getNextDayMealsAfter(
repository.getDayMeal(6),
100
).toString()
)
}
}
๋ง๋ค์ด๋๊ณ ์ญ์ ๋ฅผ ๊น๋นกํ Log ์ถ๋ ฅ์ฉ ํจ์์๋ค.
#3-3 ๋์ฒ
๋จผ์ , ViewModel์ log()๋ฅผ ์ญ์ ํ๋ค. ๊ทธ๋ฆฌ๊ณ ,
...
@Composable
fun NutrientScreen(
...
) {
LaunchedEffect(key1 = true) {
// DayMealView ์๋ ํ์ธ์ฉ ๋ก๊ทธ
viewModel.log()
...
}
...
}
NutrientScreen์ ์๋, log()๋ฅผ ํธ์ถํ๋ ์ฝ๋๋ ์ญ์ ํ๋ค.
#4 ์ฝ๋ - ์ธ๋ฒ์งธ ์๋ฌ
#4-1 SQLiteConstraintException (code 787)
FATAL EXCEPTION: main (Ask Gemini)
Process: com.example.nutri_capture_new, PID: 8547
android.database.sqlite.SQLiteConstraintException: FOREIGN KEY constraint failed (code 787 SQLITE_CONSTRAINT_FOREIGNKEY)
at android.database.sqlite.SQLiteConnection.nativeExecuteForLastInsertedRowId(Native Method)
at android.database.sqlite.SQLiteConnection.executeForLastInsertedRowId(SQLiteConnection.java:974)
at android.database.sqlite.SQLiteSession.executeForLastInsertedRowId(SQLiteSession.java:814)
at android.database.sqlite.SQLiteStatement.executeInsert(SQLiteStatement.java:89)
at androidx.sqlite.db.framework.FrameworkSQLiteStatement.executeInsert(FrameworkSQLiteStatement.kt:42)
at androidx.room.EntityInsertionAdapter.insertAndReturnId(EntityInsertionAdapter.kt:101)
at com.example.nutri_capture_new.db.MainDAO_Impl$6.call(MainDAO_Impl.java:149)
...
android.database.sqlite.SQLiteConstraintException: FOREIGN KEY constraint failed (code 787 SQLITE_CONSTRAINT_FOREIGNKEY) ๋ฌธ๊ตฌ๊ฐ, ์ธ๋ ํค ์ ์ฝ ์กฐ๊ฑด์ ์๋ฐฐํ์ ๋ ๋ฐ์ํ์์ ๋ณด์ฌ์ค๋ค. ํ ์ด๋ธ A์ ์ปฌ๋ผ a๋ฅผ ํ ์ด๋ธ B๊ฐ ์ธ๋ํค๋ก์ ๊ฐ์ง๊ณ ์๋ค๊ณ ํด๋ณด์. ์ด๋, ์๋ฌ ์ฝ๋ 787์ ์ฃผ๋ก ์ด๋ค B.a์ ๊ฐ์ A.a์์ ๋ณด์ ํ์ง ์์ ๊ฒฝ์ฐ ๋ฐ์ํ๋ ์๋ฌ๋ผ๊ณ ํ๋ค. ํ์ฌ ์์ ๋ณธ ํ๋ก์ ํธ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์คํค๋ง์์ ์ธ๋ ํค ์ ์ฝ ์กฐ๊ฑด์ด ๋ฐ์ํ ๋งํ ๊ด๊ณ๋ Day ํ ์ด๋ธ๊ณผ Meal ํ ์ด๋ธ ์ฌ์ด์์๋ค. ๋ ์ ํํ๋, Meal.dayId (= B.a)๋ฅผ Day.dayId (= A.a)์์ ๋ณด์ ํ์ง ์์๊ธฐ์ ๋ฐ์ํ๋ ์๋ฌ๋ค.
์ฌ๋ฌ ์ํ์ฐฉ์ค๋ฅผ ๊ฑฐ์ณ ์ด ๋ชจ๋ ์ฐ์์ ์๋ฌ์ ๊ทผ๋ณธ์ ์ฐพ์๋๋ฐ ๊ทธ ๋ถ๋ถ์ ์๋์ ๊ฐ๋ค.
#4-2 ๋ฐ์ ์์ธ
// in NutrientScreen.kt
LaunchedEffect(key1 = true) {
repeat(5) {
viewModel.onEvent(
NutrientViewModelEvent.InsertMeal(
meal = Meal(
time = LocalTime.now(),
name = "test",
nutritionInfo = NutritionInfo()
),
date = LocalDate.now()
)
)
}
}
// in MainRepository.kt
suspend fun insertMeal(meal: Meal, date: LocalDate): Long {
var dayId = dao.getDayId(date)
if (dayId == null) {
dayId = dao.insertDay(Day(date = date))
}
return dao.insertMeal(meal.copy(dayId = dayId))
}
View์์ InsertMeal ์ด๋ฒคํธ๋ฅผ์ 5๋ฒ ์ฐ๋ฌ์ ๋ฐ์์ํจ๋ค. ๋ฐ๋ผ์, Repository์ insertMeal() ๋ํ 5๋ฒ ์คํ๋ ๊ฒ์ด๋ค.
// in MainRepository.kt
suspend fun insertMeal(meal: Meal, date: LocalDate): Long {
var dayId = dao.getDayId(date)
if (dayId == null) {
dayId = dao.insertDay(Day(date = date))
}
Log.i("test", "date: $date\ndayId: $dayId")
return dao.insertMeal(meal.copy(dayId = dayId))
}
insertMeal()์ Log ํจ์๋ฅผ ๋ฃ๊ณ ๊ทธ ์ถ๋ ฅ์ ํ์ธํด๋ณด๋ฉด,
date: 2024-11-13
dayId: 1
date: 2024-11-13
dayId: 2
date: 2024-11-13
dayId: 3
date: 2024-11-13
dayId: 4
date: 2024-11-13
dayId: 5
๋ผ๋ ์ดํดํ๊ธฐ ํ๋ ๊ฒฐ๊ณผ๊ฐ ์ถ๋ ฅ๋๋ค. dayId - date ์์ ํ ๋ฒ ๊ฒฐ์ ๋๋ฉด, ํด๋น ๋ ์ฝ๋๊ฐ ์ญ์ ๋ ๋๊น์ง ๊ฐ์ด ๋ฐ๋์ด์ ์ ๋๋ค. date๊ฐ ์ ์ฅ๋๋ Column์ Uniqueํ๋๋ก ์ค๊ณํ๋ค. ๊ทธ๋ ๋ค๋ฉด dayId = dao.insertDay(Day(date = date))์์, ๊ฐ์ date๊ฐ ์ธ์๋ก ์ ๋ฌ๋ ๋ dayId ๋ํ ํญ์ ๊ฐ์ ๊ฐ์ ๋ฑ์ด์ผ ํ๋ค.
๊ทผ๋ณธ์ ์ธ ์์ธ์, DAO์์ ์ ์ํ insertDay()์ ๋ฌด์ฌ์ฝ ์ฌ์ฉํ onConflict ์ด๋ ธํ ์ด์ ์ด์๋ค.
// in MainDAO.kt
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertDay(day: Day): Long
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertMeal(meal: Meal): Long
๋๋ insertMeal()์ ๋ง๋ค ๋, onConflict = OnConflictStrategy.REPLACE๋ก ๋ ์ผ๋ก์จ updateMeal()์ ๋ง๋๋ ์๊ณ ๋ฅผ ๋๋ ค๊ณ ํ๋ค.
๊ทธ๋ฆฌ๊ณ insertMeal()์ ๋ง๋ค ๋์ฒ๋ผ insertDay() ๋ํ onConflict = OnConflictStrategy.REPLACE๋ก ๋์๋๋ฐ, ์ฌ๊ธฐ์์ ๋ชจ๋ ๋น๊ทน(?)์ด ํ์ํ๋ ๊ฒ์ด๋ค. onConflict = OnConflictStrategy.REPLACE์ธ insertDay()๋ updateDay()๋ฅผ ํฌํจํ ๊ฐ๋ ์ด ๋๋๋ฐ, ๋ฌธ์ ๋ updateDay()๋ ์กด์ฌํด์๋ ์ ๋๋ ํจ์์๋ค๋ ์ ์ด๋ค. Day ํ ์ด๋ธ์ ๋ ์ฝ๋๋ ํ๋ฒ ๋ฑ๋ก๋ ์ด์, ํด๋น ๋ ์ฝ๋๊ฐ ์ญ์ ๋๊ธฐ ์ ๊น์ง๋ UPDATE๋ ์ผ์ด ์ ํ ์๊ธฐ ๋๋ฌธ์ด๋ค. ์ ํ UPDATE๋์ง ์์ ๊ฒ์ด๋ผ ์๊ฐํ ํ ์ด๋ธ์, UPDATE ๋์์ ๋ดํฌํ (onConflict = OnConflictStrategy.REPLACE์ธ) insertDay()๋ฅผ ๋ง๋ฌด๊ฐ๋ด๋ก ์ฌ์ฉํ์ผ๋ ์ผ์ด๋ ํผ๋์ธ ๊ฒ์ด๋ค.
#4-3 ๋์ฒ
// in MainDAO.kt
@Query("SELECT day_id FROM day_table WHERE day_date = :date LIMIT 1")
suspend fun getDayId(date: LocalDate): Long
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insertDay(day: Day): Long
๋น๊ทน์ ํด๊ฒฐํ๊ธฐ ์ํด์ insertDay()์ onConflict๋ฅผ @Insert(onConflict = OnConflictStrategy.IGNORE)๋ก ๋ณ๊ฒฝํ๋ค.
์ด ์๋ฌ์ ๊ด๋ จ์ด ์๋ ๊ฒ์ ์๋์ง๋ง, insertDay() ๋ฐ๋ก ์์ ์๋ ๋ฉ์๋ getDayId() ๋ฐํํ์ด Long?์ธ๊ฒ ๋์ ๋ฐํ๋ค. ๋ด์น๊น์, getDayId()์ ๋ฐํํ์ Long?์์ Long์ผ๋ก ๋ณ๊ฒฝํ๋ค. ์ด๋ฌ๋ฉด ์ฟผ๋ฆฌ์ ์คํจ ์ null์ด ์๋ -1์ ๋ฑ๋๋ค. ๋์์ด ์๋ค๋ฉด ๊ตณ์ด null์ ์ฌ์ฉํ ์ด์ ๊ฐ ์๋ค. null์ ๋ง์ ์ ๊ทผ์์ด๋ค.
// in MainRepository.kt
suspend fun insertMeal(meal: Meal, date: LocalDate): Long {
val dayId = dao.insertDay(Day(date = date))
Log.i("test", "date: $date\ndayId: $dayId")
return if (dayId == -1L) {
dao.insertMeal(meal.copy(dayId = dao.getDayId(date)))
} else {
dao.insertMeal(meal.copy(dayId = dayId))
}
}
๋ ํฌ์งํ ๋ฆฌ๋ ์์ ๊ฐ์ด ์์ ํ๋ค. insertDay() ์ ์ด๋ฏธ date Column์ ์กด์ฌํ๋ date ๊ฐ์ด๋ผ๋ฉด, dayId์๋ -1์ด ๋ด๊ธด๋ค (-1L์ Longํ -1์ ์๋ฏธํ๋ค). ๋ฐ๋ผ์, dayId๊ฐ -1์ธ ๊ฒฝ์ฐ์ ์๋ ๊ฒฝ์ฐ๋ฅผ ๋๋ ๋ถ๊ธฐ๋ฌธ์ ์์ฑํ๋ค.
์ฑ์ ์ฌ์ค์น ํ ๋ค์ ์คํ์์ผ๋ณด๋ฉด,
date: 2024-11-13
dayId: 1
date: 2024-11-13
dayId: -1
date: 2024-11-13
dayId: -1
date: 2024-11-13
dayId: -1
date: 2024-11-13
dayId: -1
๋ผ๋ ๋ก๊ทธ ๋ฉ์์ง๊ฐ ๋ฌ๋ค.
#5 Log.i( ... ) ์ญ์
์๋ฌ ํ์ธ ๋ฐ ์ ์ ์๋ ํ์ธ์ ์ํด ์ฝ์ ํ๋ Log.i( ... )๋ค์ ์ ๊ฑฐํ๋ค.
#6 ์์ฝ
์ฑ์ด ์คํ๋์ง ์๋ ์๋ฌ๋ฅผ ํด๊ฒฐํ๋ค.
#7 ์์ฑ๋ ์ฑ
#7-1 ์คํฌ๋ฆฐ์ท

NutrientScreen์ LaunchedEffect()์ ์ํด ๋งค ์คํ๋ง๋ค Meal ๊ฐ์ฒด๊ฐ 5๊ฐ์ฉ INSERT๋๋ค.
#7-2 ์ด ๊ฒ์๊ธ ์์ ์ Commit
GitHub - Kanmanemone/nutri-capture-new
Contribute to Kanmanemone/nutri-capture-new development by creating an account on GitHub.
github.com
#7-3 ๋ณธ ํ๋ก์ ํธ์ ๊ฐ์ฅ ์ต์ Commit
GitHub - Kanmanemone/nutri-capture-new
Contribute to Kanmanemone/nutri-capture-new development by creating an account on GitHub.
github.com