App 개발 일지 25

Nutri Capture 백엔드 - Room 무결성 보완

#1 개요이전 게시글의 Commit은 앱 실행이 안되는 버그가 있다. 해당 버그를 발생시킨 근본적인 부분을 찾아간다. 특히, 세번째 에러는 Room의 무결성과 관련된 에러로 해결까지 꽤 시간이 소요됐으며, 동시에 Room 구현에 있어 DAO를 경솔히 작성해서는 안된다는 교훈을 얻었다. #2 코드 - 첫번째 에러#2-1 NoSuchElementExceptionFATAL EXCEPTION: main (Ask Gemini)Process: com.example.nutri_capture_new, PID: 7679java.util.NoSuchElementException: List is empty. at kotlin.collections.CollectionsKt___CollectionsKt.last(_Coll..

Nutri Capture 프론트엔드 - 채팅UI 구조 잡기

#1 개요이전 게시글에서 채팅 UI 도입을 위해 가상 테이블 DayMealView를 만들었다. 백엔드 작업이 끝났으므로, Day 테이블 및 Meal 테이블을 참조하던 프론트가 이제는 DayMealView 테이블을 참조하도록 만들어야 한다. #2 코드 - 백엔드 깨알 변경 사항#2-1 DAO에서 getAllDayMeals(limit: Int) 선언...@Daointerface MainDAO { ... @Query("SELECT * FROM DayMealView LIMIT :limit") suspend fun getAllDayMeals(limit: Int): List ...}존재하는 모든 레코드를 가져오는 getAllDayMeals()의 메소드 오버로딩이다. getAllDayMeals(..

Nutri Capture 백엔드 - Room 가상 테이블의 레코드를 필요한 만큼만 가져오기

#1 개요#1-1 우아함이전 게시글에서 만든, 가상 테이블을 참조하는 DAO 함수는 가상 테이블의 모든 레코드를 통째로 가져오는 방식이었다. 하지만 이는 우아함과는 거리가 있는 방식이다. 처음에는 화면을 가득 채울 정도의 레코드만 가져오고, 무한 스크롤을 통해서 필요한 만큼만 또 가져오는 방식을 구현할 것이다. "View가 보유한 마지막 레코드가 무엇인지 알고, 해당 레코드를 기반으로 (테이블 위치 상) 더 뒤에 있는 레코드들을 뽑아오면 될 것이다."라고 처음에는 간단하게만 생각했다. 그러나 시행착오를 겪으며 시간이 오래 걸렸다 (고작 DAO 함수하나 만드는 건데 이전 게시글과 별도의 게시글로 분리할 이유기도 하다). #1-2 첫 시도: Cursor Room DAO를 사용하여 데이터 액세스  |  And..

Nutri Capture 백엔드 - Room 가상 테이블 선언

#1 개요#1-1 채팅 UI 구현을 위한 첫 걸음'채팅 UI'에서 표시할 데이터를 하나의 레코드로서 가지는 가상 테이블을 선언한다. #1-2 가상 테이블 (SQLite)SELECT day_table.day_id AS day_id, meal_table.meal_id AS meal_id, day_table.day_date AS day_date, meal_table.meal_time AS meal_time, meal_table.meal_name AS meal_name, /* NutritionInfo의 Column 시작 */ meal_table.overeating_excess AS overeating_excess, meal_table...

Nutri Capture 방향성 - 채팅 UI

#1 기존 UI 비판#1-1 스크린샷지금까지 진행한 프로젝트의 스크린샷이다. 아래는 이 화면의 문제점들이다. #1-2 중첩된 Container'날짜 카드' Container 안에는 '식단' Container가 들어가는데, 이렇게 포장지가 2중으로 들어가면 사용자 입장에서 피로감이 생기기 쉽다.  #1-3 '0으로 처리된' 여백'식단'이 없는 '날짜'도 Container가 생긴다. 그러한 '날짜' Container는 아무런 정보 없이 (정확히는 해당 날짜에 기록된 '식단'이 없다는 정보를 제외하고) 자리만 차지한다. 이른바 '0으로 처리된' 여백이다. 정보가 없으면 생략(null)하면 됨에도, 생략하지 않았다(0)는 의미다. #1-4 낯선 스키마(Schema)낯설다. 제일 단순하면서도 제일 주요한 단점이다..

Nutri Capture 백엔드 - View에서 INSERT 트리거

#1 개요지금까지의 과정을 통해, ViewModel에서 Model을 참조할 수 있게 되었다. 본 게시글에선 ViewModel에서 Model을 참조하는 이벤트를 구현하고, View에서 해당 이벤트를 Trigger하게 만들어본다. #2 코드#2-1 ViewModel의 이벤트 구현...class NutrientViewModel(private val repository: MainRepository) : ViewModel() {    ...    // (4) View로부터 받은 이벤트 처리    fun onEvent(event: NutrientViewModelEvent) {        when (event) {            ...            is NutrientViewModelEvent.Inse..

Nutri Capture 백엔드 - Model을 ViewModel에 생성자 주입

#1 개요이전 게시글에서 구축한 Room을 ViewModel에 생성자 주입하여, Room(Model)을 참조할 수 있게 만든다. #2 코드 - 이벤트 추가#2-1 이벤트 추가// package com.example.nutri_capture_new.nutrientimport com.example.nutri_capture_new.db.Mealimport java.time.LocalDatesealed class NutrientViewModelEvent { data object InitializeState : NutrientViewModelEvent() data object LoadMoreItemsAfterLastDate : NutrientViewModelEvent() data object Lo..

Nutri Capture 백엔드 - Room의 @DAO, @Database 구현

#1 개요이전 게시글에 이어, Room의 남은 부분을 구현한다. #2 코드#2-1 @DAO// package com.example.nutri_capture_new.dbimport androidx.room.Daoimport androidx.room.Deleteimport androidx.room.Insertimport androidx.room.OnConflictStrategyimport androidx.room.Queryimport java.time.LocalDate@Daointerface MainDAO { @Query("SELECT day_id FROM day_table WHERE day_date = :date LIMIT 1") suspend fun getDayId(date: LocalDate..

Nutri Capture 백엔드 - 새 ERD와 Room의 @Entity 정의

#1 개요#1-1 이전 게시글 폐기ERD에 대해 다뤘던 이전 게시글은 앱의 방향성을 재고한 이 게시글과 상충된다. 따라서 이전에 만들었던 ERD대로 데이터베이스의 스키마를 형성하지 않을 것이다. 새 ERD는 아래와 같다. #1-2 새 ERDmeal_table과 nutrition_info_table이 1 : n 관계인 것처럼 되어있는데 실제로는 그렇지 않다. nutrition_info_table은 meal_table의 기본키를 외래키이자 기본키로 쓰는, 식별 관계의 자식 테이블이기 때문에 1 : 1 관계다. 위 ERD 이미지는 DBeaver를 통해 뽑아낸 것인데 아마 오류가 난 것 같다. 아니면 1..n라는 표기가 1 : 1과 1 : n을 모두 아우르는 표현식일까? 아무튼 그렇다.-- Day 테이블CREA..

Nutri Capture 프론트엔드 - Card

#1 개요스크롤 관련해서는 한시름 놓았다. 본 게시글에선 LazyColumn의 각 아이템을 Card로 감싸고 약간의 디자인적 수정을 할 것이다. 본 게시글까지해서 프론트엔드는 중간 맺음을 할 것이고 다음 게시글부터는 아마 백엔드 구현으로 넘어갈듯 싶다. #2 코드#2-1 (틀 잡기) Card()... @Composable fun NutrientScreen( ... ) { LaunchedEffect(key1 = true) { // State 초기화 ... // ViewModel로부터 받은 이벤트 처리 ... } LaunchedEffect(key1 = viewModel.isInitialized.value) { ... } LazyColumn( ... ) { val dailyMeals = viewModel.nu..