티스토리 뷰

Room

  • Room은 구글에서 제공하는 공식 ORM(Object-realational mapping)이다
  • Room 라이브러리를 사용하면 실행 기기에 앱 데이터 캐시를 만들고 네트워크 연결 여부와 관계없이 보여지게 할 수 있다!
  • Room은 그림과 같은 3가지 요소들로 구성된다

  • DataBase : 데이터베이스 접근 지점을 제공하며 DAO를 관리한다
  • Dao : 데이터베이스에 접근할 수 있는 함수들이 정의되어 있으며 이 함수들은 쿼리문을 이용해 구현해줘야 한다
  • Entity : 데이터베이스 내의 DB 테이블을 나타낸다

Room 적용하기

✔️build.gradle(app)

dependencies {
      def room_version = "2.2.5"

      implementation "androidx.room:room-runtime:$room_version"
      annotationProcessor "androidx.room:room-compiler:$room_version" // For Kotlin use kapt instead of annotationProcessor

      // optional - Kotlin Extensions and Coroutines support for Room
      implementation "androidx.room:room-ktx:$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"

      // Test helpers
      testImplementation "androidx.room:room-testing:$room_version"
}
  • 언제나 제일 먼저 app레벨의 gradle에 종속항목을 추가해준다

✔️RecentPlaceEntity

@Entity(tableName = "recentPlace")
data class RecentPlaceEntity (
    @PrimaryKey(autoGenerate = true) var id : Int = 0,
    @ColumnInfo(name = "placeName") val placeName: String
)
  • 나는 최근 검색 기록 저장 기능이 필요한데, 검색어만 보여주면 되므로 거기에 맞게 테이블을 만들어줬다
  • Entity 클래스를 정의할 때 적절한 annotation을 사용해야 한다
    • @Entity(tableName = "recentPlace") : 테이블명을 recentPlace로 정의한다
    • @PrimaryKey : 해당 annotation이 붙은 entity를 primaryKey로 지정한다
    • @ColumnInfo(name = "placeName") : 해당 칼럼명을 placeName으로 정의한다
    • @Nonnull : entity의 값이 절대 null이 될 수 없음을 뜻한다
  • 모든 테이블에는 반드시 PrimaryKey가 필요한데, 나는 (autoGenerate = true) 속성을 이용해서 따로 정의하지 않아도 자동으로 증가하며 저장되는 entity를 정의했다

✔️BaseDao

interface BaseDao<T> {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insert(obj : T)

    @Delete
    fun delete(obj : T)

    @Update(onConflict = OnConflictStrategy.ABORT)
    fun update(obj : T)
}
  • 가장 기본적인 삽입, 삭제, 수정 세 개의 메소드를 포함하는 BaseDao를 인터페이스로 만들어줬다
  • Dao를 정의할 때 이 인터페이스를 상속받으면 override 없이 해당 함수에 바로 접근할 수 있다!

✔️RecentPlaceDao

@Dao
interface RecentPlaceDao : BaseDao<RecentPlaceEntity>{
    @Query("SELECT * FROM recentPlace ORDER BY id DESC")
    fun loadRecentPlace(): LiveData<List<RecentPlaceEntity>>
}
  • RecentPlaceDao에는 최근 검색 기록을 가져올 때 필요한 메소드를 정의해주었다
  • Observe해서 검색 진행중에도 데이터 변경 시 바로 업데이트해줄 수 있도록 List형태의 LiveData를 반환하는 함수로 정의해줬다

✔️RecentPlaceDataBase

@Database(entities = [RecentPlaceEntity::class], version = 1)
abstract class RecentPlaceDataBase : RoomDatabase() {
    abstract fun recentPlaceDao() : RecentPlaceDao

    companion object {

        private var instance: RecentPlaceDB? = null

        @Synchronized
        fun getInstance(context: Context): RecentPlaceDB? {
            if (instance == null) {
                instance = Room.databaseBuilder(context.applicationContext,
                    RecentPlaceDB::class.java, "RecentPlaceDB")
                    .fallbackToDestructiveMigration()
                    .allowMainThreadQueries()
                    .build()
            }
            return instance
        }
    }
}
  • DataBase 클래스는 반드시 RoomDataBase를 상속받는 추상클래스로 정의해야 한다
  • annotation에는 데이터베이스와 연결할 entity를 배열 형태로 지정해준다
  • RoomDataBase 인스턴스는 리소스를 아주 많이 소비하므로 Room.databaseBuilder()를 이용해 싱글톤으로 생성해줘야한다
  • 싱글톤 중복생성을 막기 위해 인스턴스 생성 메소드에 @Synchronized 주석을 사용했다

✔️PlaceSearchRepository

class PlaceSearchRepository{

    private val database = RecentPlaceDB.getInstance(EarlyBuddyApplication.getGlobalApplicationContext())!!
    private val recentPlaceDao = database.recentPlaceDao()

    fun insert(recentPlace: RecentPlaceEntity) {
        InsertAsyncTask(recentPlaceDao).execute(recentPlace)
    }

    fun loadRecentPlace(): LiveData<List<RecentPlaceEntity>>{
        return recentPlaceDao.loadRecentPlace()
    }

    private class InsertAsyncTask<TE, TDO : BaseDao<TE>>(private val dao: TDO) : AsyncTask<TE, Void, Void>() {
        override fun doInBackground(vararg entity: TE): Void? {
            dao.insert(entity[0])
            return null
        }
    }
}
  • 함수 호출 시 백그라운드에서 작동하도록 AsyncTask를 이용해 함수를 구현해준다

✔️PlaceSearchViewModel

class PlaceSearchViewModel : BaseViewModel(EarlyBuddyApplication.getGlobalApplicationContext()) {

    private val repository : PlaceSearchRepository = PlaceSearchRepository()
    val places: LiveData<List<RecentPlaceEntity>>

    fun insert(recentPlace : RecentPlaceEntity) {
        repository.insert(recentPlace)
    }

    init {
        places = repository.loadRecentPlace()
    }
}
  • activity를 처음 띄웠을 때 최근 검색 리스트를 띄울 수 있도록 init에서 select 쿼리문을 실행하는 함수를 불러와 LiveData를 업데이트 해준다
  • insert 함수에서는 repository에 구현된 insert 함수에 접근한다

✔️PlaceSearchActivity

viewModel.insert(RecentPlaceEntity(placeName =  act_start_place_search_et_search.text.toString()))
  • 검색 결과를 최근 검색 기록에 저장하는 코드이다
  • Room 데이터를 사용할 activity에서는 뷰모델 객체를 이용해 PlaceSearchViewModel에 정의되어 있는 insert 함수에 접근한다
  • id는 autoGenerate로 정의했으므로 값을 따로 넣어줄 필요 없다
viewModel.places.observe(this, Observer {
	(viewDataBinding.actStartPlaceSearchRv.adapter as BaseRecyclerViewAdapter<RecentPlaceEntity, ItemRecentPlaceBinding>)
		.replaceAll(it)
	(viewDataBinding.actStartPlaceSearchRv.adapter as BaseRecyclerViewAdapter<RecentPlaceEntity, ItemRecentPlaceBinding>)
		.notifyDataSetChanged()
})
  • 또한, activity에서 LiveData를 Observe해서 데이터 변경이 일어날 시 바로 리싸이클러뷰에 반영해줄 수 있게 했다!

완성 >0<


참고자료🕊

 

Room 라이브러리 사용 [2]

👍 Repository 만들기 Repository를 작성하여 실제 호출후 사용할 수 있도록 해준다. AnyncTask는 백그라운드에서 작동하는 쓰레드인데 이를 사용하지 않고 UI-thread에서 작동하면 오류가 발생한다. 이 Rep

velog.io

 

googlecodelabs/android-room-with-a-view

Contribute to googlecodelabs/android-room-with-a-view development by creating an account on GitHub.

github.com

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/07   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
글 보관함