๊ฐœ๋ฐœ ์ผ์ง€ ๐Ÿ’ป/Nutri Capture

Nutri Capture ๋ฐฑ์—”๋“œ - Room ๋ฌด๊ฒฐ์„ฑ ๋ณด์™„

interfacer_han 2024. 11. 15. 00:06

#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