#1 개요
이전 게시글에 이어, Room의 남은 부분을 구현한다.
#2 코드
#2-1 @DAO
// package com.example.nutri_capture_new.db
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import java.time.LocalDate
@Dao
interface MainDAO {
@Query("SELECT day_id FROM day_table WHERE day_date = :date LIMIT 1")
suspend fun getDayId(date: LocalDate): Long?
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertDay(day: Day): Long
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertMeal(meal: Meal): Long
@Delete
suspend fun deleteMeal(meal: Meal): Int
@Query("""
SELECT meal_table.*
FROM meal_table
INNER JOIN day_table ON meal_table.day_id = day_table.day_id
WHERE day_table.day_date = :targetDate
ORDER BY meal_table.meal_time ASC
""")
suspend fun getMealsOrderedByTime(targetDate: LocalDate): List<Meal>
@Query("SELECT COUNT(*) FROM meal_table WHERE day_id = :dayId")
suspend fun getMealCountForDay(dayId: Long): Int
@Query("DELETE FROM day_table WHERE day_id = :dayId")
suspend fun deleteDay(dayId: Long)
}
처음부터 이 모든 함수들을 생각해낸 것은 아니다. 본 게시글 이후 게시글에서 INSERT, SELECT 등을 View에서부터 Trigger하는 코드를 만들었는데 해당 코드를 만들며 이 DAO의 함수들과 아래에 있는 Repository의 함수들을 하나하나 만들어냈다. 즉, 지금은 @Dao 어노테이션이 붙은 인터페이스 하나만 정의하고 넘어가도 상관이 없다는 말이다.
#2-2 Repository
// package com.example.nutri_capture_new.db
import java.time.LocalDate
class MainRepository(private val dao: MainDAO) {
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))
}
suspend fun deleteMeal(meal: Meal): Int {
val deletedRowCount = dao.deleteMeal(meal)
if (deletedRowCount == 0) {
return 0
} else {
val mealCount = dao.getMealCountForDay(meal.dayId)
if (mealCount == 0) {
dao.deleteDay(meal.dayId)
}
return deletedRowCount
}
}
suspend fun getMealsOrderedByTime(targetDate: LocalDate): List<Meal> {
return dao.getMealsOrderedByTime(targetDate)
}
}
insertMeal( ... )
Meal을 INSERT하려고 시도할 때 부모 테이블 즉, Day 테이블이 존재하지 않으면 에러가 날 것이다. 따라서, Day 테이블이 없다면 먼저 Day 테이블부터 생성하는 코드를 집어넣는다.
deleteMeal( ... )
Meal이 하나도 없는 Day는 존재 이유가 없다. 따라서, Meal이 하나도 없다면 Day 테이블을 삭제한다.
getMealsOrderedByTime( ... )
어떤 날(Day)의 먹은 것들(List<Meal>)을 오름차순(#2-1의 DAO 코드 참조)으로 가져온다.
#2-3 TypeConverter
Room은 LocalDate형 및 LocalTime형 변수를 내부적으로 저장하지 못한다. 따라서 위 공식문서의 가이드를 따라 아래 있는 TypeConverter 클래스를 만들었다.
// package com.example.nutri_capture_new.db
import androidx.room.TypeConverter
import java.time.LocalDate
import java.time.LocalTime
import java.time.format.DateTimeFormatter
class Converters {
private val dateFormatter = DateTimeFormatter.ISO_LOCAL_DATE
private val timeFormatter = DateTimeFormatter.ISO_LOCAL_TIME
// LocalDate -> String
@TypeConverter
fun fromLocalDate(date: LocalDate?): String? {
return date?.format(dateFormatter)
}
// String -> LocalDate
@TypeConverter
fun toLocalDate(dateString: String?): LocalDate? {
return dateString?.let { LocalDate.parse(it, dateFormatter) }
}
// LocalTime -> String
@TypeConverter
fun fromLocalTime(time: LocalTime?): String? {
return time?.format(timeFormatter)
}
// String -> LocalTime
@TypeConverter
fun toLocalTime(timeString: String?): LocalTime? {
return timeString?.let { LocalTime.parse(it, timeFormatter) }
}
}
#2-4 @Database
// package com.example.nutri_capture_new.db
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
@Database(
entities = [
Day::class,
Meal::class
],
version = 1
)
@TypeConverters(Converters::class)
abstract class MainDatabase : RoomDatabase() {
abstract val mainDAO: MainDAO
companion object {
@Volatile
private var INSTANCE: MainDatabase? = null
fun getInstance(context: Context): MainDatabase {
synchronized(this) {
var instance = INSTANCE
if (instance == null) {
instance = Room.databaseBuilder(
context.applicationContext,
MainDatabase::class.java,
"main_database"
).build()
INSTANCE = instance
}
return instance
}
}
}
}
이 게시글에 있는 Database 상용구 코드를 그대로 사용했다. #2-3에서 만든 TypeConverter를 추가하는 구문(@TypeConverters(Converters::class))도 넣어준다.
#3 요약
Room의 Entity 부분을 제외한 나머지(Dao와 Database 그리고 TypeConverter와 Repository)를 구현했다.
#4 완성된 앱
#4-1 이 게시글 시점의 Commit
#4-2 본 프로젝트의 가장 최신 Commit
'App 개발 일지 > Nutri Capture' 카테고리의 다른 글
Nutri Capture 백엔드 - View에서 INSERT 트리거 (0) | 2024.10.23 |
---|---|
Nutri Capture 백엔드 - Model을 ViewModel에 생성자 주입 (0) | 2024.10.23 |
Nutri Capture 백엔드 - 새 ERD와 Room의 @Entity 정의 (1) | 2024.10.23 |
Nutri Capture 프론트엔드 - Card (0) | 2024.10.17 |
Nutri Capture 프론트엔드 - 스크롤 로직 View에 일임 (0) | 2024.10.16 |