본문 바로가기

Android/Android의 모든 것

👋Android의 모든 것 : 1️⃣Room, 2️⃣예제(Koin, Room)

728x90

https://goni95.tistory.com/173

 

👋Android에 모든 것 | Droid Knights 2021

목  차 예제 Android 👋Android에 모든 것 : 1️⃣Android, 2️⃣Android SDK, 3️⃣Platform Architecture 👋Android에 모든 것 개념 : 1️⃣App Manifest, 2️⃣Android Context, 3️⃣App Components 1 O 👋An..

goni95.tistory.com

 

1️⃣Room

Room은 내부적으로 *SQLite를 사용하여 SQLite의 모든 기능을 제공하고 DB 접근의 편의성을 높여주는 *ORM(Object Relational Mapping) 라이브러리로 DB 데이터를 Java 또는 Kotlin 객체로 *mapping할 수 있습니다.

LiveData, Rx, Coroutine Flow로 데이터를 관찰할 수 있도록 구축되어 있고, 컴파일 시 코드들을 자동으로 생성해주는 다양한 Annotation을 지원합니다.

Room은 64kb인 realm에 비해 적은 용량이고 메서드의 수가 적어 dex 크기 제한에도 고민하지 않아도 되지만 쿼리문을 작성할 줄 알아야 합니다.

 

사용법

아래 예제는 Koin + Room + Repository Pattern으로 구현한 예제입니다. Koin과 Repository을 설정하는 부분과 관련된 코드는 포함하고 있지않기 때문에 둘 다 모른다면 예제를 실행해보기가 어렵습니다. Koin에 대해선 알지만 Repositorty Pattern을 모르신다면 FoodNtrIrdntRepositoryImpl 대신 ViewModel에서 서 주입받도록 변경하시면 됩니다.

 

1. module 수준의 build.gradle 파일에 Room 라이브러리 중속성 추가

Room 2.1 버전 부터는 @Insert, @Delete, @Update annotation이 달린 DAO 메서드와 반환 유형이 Rx Completable, Single<T>, Maybe<T> 그리고 Coroutine Flow를 지원합니다.

    def  room_version = "2.3.0"
    implementation "androidx.room:room-runtime:$room_version"
    kapt "androidx.room:room-compiler:$room_version"
    implementation "androidx.room:room-ktx:$room_version"   // optional - Kotlin Extensions and Coroutines support for Room

 

2. DB 내의 Table 역할을 하는 class 생성

import androidx.room.*

@androidx.room.Entity(tableName = "foodNtrIrdntTable")
data class FoodNtrIrdntEntity (
    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = "id")
    override val id: Long?,
    @ColumnInfo(name = "company")
    val company: String,
    @ColumnInfo(name = "begin_year")
    val beginYear: String,
    @ColumnInfo(name = "description_kor")
    val descriptionKOR: String,
    @ColumnInfo(name = "calorie")
    val calorie: Double,
    @ColumnInfo(name = "carbohydrate")
    val carbohydrate: Double,
    @ColumnInfo(name = "protein")
    val protein: Double,
    @ColumnInfo(name = "fat")
    val fat: Double,
    @ColumnInfo(name = "sugar")
    val sugar: Double,
    @ColumnInfo(name = "salt")
    val salt: Double,
    @ColumnInfo(name = "cholesterol")
    val cholesterol: Double,
    @ColumnInfo(name = "saturated_fatty_acid")
    val saturatedFattyAcid: Double,
    @ColumnInfo(name = "trans_fat")
    val transFat: Double,
    @ColumnInfo(name = "serving_weight")
    val servingWeight: Int,
    @ColumnInfo(name = "serving_count")
    val servingCount: Int
    ) : Entity
interface Entity {
    val id: Long?
}

 

3. 데이터베이스의 접근을 위해서 추상 인터페이스를 제공하는 객체인 DAO class 생성

import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import sang.gondroid.calingredientfood.data.dto.entity.FoodNtrIrdntEntity

@Dao
interface FoodNtrIrdntDao {
    @Query("SELECT * FROM foodNtrIrdntTable WHERE description_kor LIKE :value")
    suspend fun getSearchList(value: String): List<FoodNtrIrdntEntity>

    @Insert
    suspend fun insert(foodNtrIrdntEntity: FoodNtrIrdntEntity)
}

 

4. DB의 전체적인 소유자 역할을 하며, DB 생성 및 버전 관리를 위한 class 생성

import androidx.room.Database
import androidx.room.RoomDatabase
import sang.gondroid.calingredientfood.data.dto.entity.FoodNtrIrdntEntity

/**
 * [22.03.25] : RoomDatabase를 생성하고 관리하는 DB 객체
 *              entities : 현재 DB 관련 Entity를 정의
 *              version : DB 버전 정의 / Schema 변경 시 version도 바뀌어야함
 *              exportSchema : Room에 Schema 구조를 폴더로 Export(내보내다) 할 것인지 설정
 */
@Database(
    entities = [FoodNtrIrdntEntity::class],
    version = 1,
    exportSchema = false
)
abstract class ApplicationDatabase : RoomDatabase() {

    abstract fun foodNtrIrdntDao() : FoodNtrIrdntDao
}

 

5. Koin을 통한 ApplicationDatabase와 FoodNtrIrdntDao 의존성 주입

internal val appModule = module {
    /**
     * Room
     */
    single { provideDB(androidApplication()) }
    single { provideFoodNtrIrdntDao(get()) }
    
     /**
     * Coroutine Dispatchers
     * Gon [22.01.12] : Coroutine을 Dispatcher에 전달하면 dispatcher가 자신이 관리하는 Thread Pool 내의 Thread에 분배
     */
    single { Dispatchers.IO }
    single { Dispatchers.Main }
    
    //주입받는 객체
    single<FoodNtrIrdntRepository> { FoodNtrIrdntRepositoryImpl(get()) }
}

private fun provideDB(context : Context) : ApplicationDatabase =
    Room.databaseBuilder(context, ApplicationDatabase::class.java, API.DB_NAME).build()

private fun provideFoodNtrIrdntDao(database: ApplicationDatabase) = database.foodNtrIrdntDao()

 

6. 주입받는 DAO 객체를 통한 DAO 메서드 호출

/**
 * Gon [22.01.12] : Domain Layer의 FoodNtrIrdntRepository 구현체
 */
class FoodNtrIrdntRepositoryImpl(
    private val ioDispatcher: CoroutineDispatcher,
    private val foodNtrIrdntDao: FoodNtrIrdntDao
) : FoodNtrIrdntRepository {

    override suspend fun getCustomFoodNtrIrdntList(value: String): List<FoodNtrIrdntEntity> =
        withContext(ioDispatcher) {
            foodNtrIrdntDao.getSearchList(value)
        }

    override suspend fun insertCustomFoodNtrIrdnt(foodNtrIrdntEntity: FoodNtrIrdntEntity) =
        withContext(ioDispatcher) {
            foodNtrIrdntDao.insert(foodNtrIrdntEntity)
        }
}

 

 

*mapping : 두 개의 서로 다른 데이터간의 관계를 정의해 하나의 데이터가 다른 데이터를 가르키도록 하는 것 입니다.

 

*ORM : 서로 다른 객체와 테이블에 대한 불일치를 해결해주는 별도의 쿼리문 없이도 객체를 통해 간접적으로 데이터베이스의 데이터를 다룰 수 있도록 객체와 관계형 데이터베이스를 연결해주는(*mapping) 프레임워크입니다.

 

*Annotation : 안드로이드 개발 시 Annotation은 특정 클래스, 변수, 메소드 등에 붙여 해당 타겟의 기능을 좀 더 명확하게 해주는 역할을 합니다. Kotlin 또는 Android에 내장된 built in annotation과 Annotation에 대한 정보를 나타내기 위한 Annotation인 meta annotation, 개발자가 직접 만드는 custom annotation이 있습니다.

 

*SQLite : Android 플랫폼에 사용되는 오픈 소스, 서버리스 데이터베이스로 데이터베이스 파일의 저장 루트가 애플리케이션의 내부에 있어 어플리케이션을 삭제하면 해당 파일도 함께 삭제됩니다.

( tip. 저장 루트가 data/data/패키지명/databases/db명.db가 아닌 패키지 외부에 있는 경우나 매니페스트에서 allowBackup 태그를 true인 경우 삭제되지 않습니다. )