본문 바로가기

안드로이드/코틀린

[Android/Kotlin] Room으로 앱내 데이터 저장하기

반응형

Room은 내부 디비 라이브러리로 AAC(Android Architecture Component)중 하나로 많은 분들이 사용하고 있습니다. 

>> Gradle 설정 (androidX 기준)

최신버전은 공식 문서를 참고해주세요.

    def room_version = "2.2.5"
    
    // room
    implementation "androidx.room:room-runtime:$room_version"
    kapt  "androidx.room:room-compiler:$room_version" 
    androidTestImplementation "androidx.room:room-testing:$room_version"
    // optional - RxJava support for Room
    implementation "androidx.room:room-rxjava2:$room_version"
    // optional - Guava support for Room, including Optional and ListenableFuture
    implementation "androidx.room:room-guava:$room_version"
    debugImplementation 'com.amitshekhar.android:debug-db:1.0.6'// room debug tool
    // optional - Coroutines support for Room
//    implementation "androidx.room:room-coroutines:$room_version"
    // Test helpers
    testImplementation "androidx.room:room-testing:$room_version"

>> 룸 구성요소 

Database

룸의 버전과 Entity, Dao를 정의하고 관리하는 클래스로 룸에 접근할땐 해당 클래스를 거칩니다. 

@Database(version = 1, entities = [PlantData.Item::class,DiaryData.Item::class])
abstract class DB : RoomDatabase() {

    abstract fun plantDao(): PlantData.DAO
    abstract fun diaryDao() : DiaryData.DAO

    companion object {
        private val dbName = "plantdiary.db"
        private var INSTANCE: DB? = null

        fun getInstance(context: Context): DB {
            return INSTANCE ?: synchronized(DB::class) {
                INSTANCE ?: Room.databaseBuilder(
                    context.applicationContext,
                    DB::class.java,
                    dbName
                )
                    .build().also { INSTANCE = it }
            }
        }

        fun destroyInstance() {
            INSTANCE = null
        }
    }
}

Dao

  • Database에 접근하는 메소드들을 관리하는 클래스로 쿼리문을 직접 작성할 수 있지만 기본적인 Select, Update, Insert, Delete를 Annotation으로 제공합니다.
  • LiveData, Flowable등 다양한 리턴 타입을 제공합니다.
  • 같은 아이템을 insert했을시 onConflict로 대체하거나 무시하도록 설정할 수 있습니다.
    @Dao
    interface DAO : BaseDAO<Item> {
        @Query("SELECT * FROM $TABLE_NAME WHERE year = :year AND month = :month AND plantId = :plantId")
        fun select(year: Int, month: Int, plantId: Long): LiveData<List<Item>>

        @Query("SELECT * FROM $TABLE_NAME WHERE year = :year AND month = :month AND day = :day AND  plantId = :plantId")
        fun selectForDay(year: Int, month: Int,day: Int, plantId: Long): Flowable<List<Item>>

        @Query("DELETE FROM $TABLE_NAME WHERE plantId=:plantId")
        fun deleteForPlantId(plantId: Long)

        @Query("UPDATE $TABLE_NAME SET content = :content, imgPath = :imgPath, gravity = :gravity WHERE year = :year AND month = :month AND day = :day AND  plantId = :plantId")
        fun updateDiary(year: Int, month: Int,day: Int, plantId: Long, content: String, imgPath: String, gravity: Int) : Completable

        @Query("UPDATE $TABLE_NAME SET isGiveWater = :isGiveWater WHERE year = :year AND month = :month AND day = :day AND  plantId = :plantId")
        fun updateWater(year: Int, month: Int,day: Int, plantId: Long, isGiveWater: Boolean)

        @Insert(onConflict =  OnConflictStrategy.REPLACE)
        fun insertDiary(item : Item) : Completable
    }
    
    

Entity

  • Database 테이블을 의미한다. 이름을 지정하지 않는 경우 클래스 이름이 Entity의 이름으로 저장되며, 대소문자는 구분하지 않는다. 
  • data class를 이용해서 생성하며 room에서 접근해야 하기 때문에 private으로 필드를 선언하면 안됩니다.
  • PrimaryKey는 필드 앞에 Annotation을 사용하는 방법과 Entity Annotation에 파라미터로 넣어줄수도 있습니다.
  • 각 컬럼들도 테이블 명과 같이 별도 이름을 지정하지 않는 경우는 선언된 필드 명의 이름을 따르고, ColumnInfo Annotation을 이용해 이름과 디폴트값 외 기타 설정들을 할 수 있다.
    @Entity(tableName = "diary")
    data class Item(
        @PrimaryKey(autoGenerate = true) var id: Int?= null,
		@ColumnInfo(name = "img_path",defaultValue = "") 
        var imgPath: String? = null,
        var content: String? = null,
        var year: Int,
        var month: Int,
        var day: Int,
        var gravity : Int? = null,
        var plantId: Long? = null,
        var isGiveWater : Boolean? = null
    )
@Entity(primaryKeys = arrayOf("firstName", "lastName"))
data class User(
    val firstName: String?,
    val lastName: String?
)

저는 EntityDao를 한 클래스에 생성해서 관리합니다. Entity의 경우 필드만 관리되어 별도로 분리해서 쓰기엔 조금 과한 느낌이고 DAO에서 쿼리를 작성할때 필드들을 참고하기 편합니다. 

object DiaryData {

    const val TABLE_NAME = "diary"

    @Entity(tableName = "diary")
    data class Item(
        @PrimaryKey(autoGenerate = true) var id: Int?= null,
        ....
    )


    @Dao
    interface DAO : BaseDAO<Item> {
   		.....
        @Insert(onConflict =  OnConflictStrategy.REPLACE)
        fun insertDiary(item : Item) : Completable
    }
}

.....

// 실제 사용하기 
var selectPlant: MutableLiveData<PlantData.Item> = MutableLiveData()

>> 디버깅

facebook에서 제공하는 steho 라이브러리로 저장된 룸 데이터를 확인할 수 있습니다. 적용 방법은 제 포스팅을 참고해주세요. 

 

mvvm 패턴에 관심을 가지면서 알게된 Room을 도입해서 사용한지 좀 됐지만 이번에 마이그레이션 하면서 겪은 내용들을 정리하면 좋겠다고 생각해 늦게나마 포스팅합니다.

마이그레이션 관련 이슈는 이후에 작성해서 올리도록 하겠습니다. 

반응형