Android - RoomDB를 이용한 MVVM 아키텍처 디자인 패턴 구축 (Kotlin) (2)
업데이트:
- 연구주제 : Android - RoomDB를 이용한 MVVM 아키텍처 디자인 패턴 구축 (Kotlin)
- 연구목적 : 안드로이드에서의 코틀린 활용
- 연구일시 : 2020년 03월 06일 09:00~17:00
- 연구자 : 이재환 ljh951103@naver.com
- 연구장비 : HP EliteDesk 800 G4 TWR, Kotlin, Android studio, IntelliJ
- 관련연구 : Java, Android, Kotlin, AAC, LiveData, ViewModel
서론
MVVM 패턴
지난 포스팅에서 MVVM 패턴에 대해 설명하였다.
이번 포스팅에선 직접 안드로이드상에서 RoomDB를 생성하고 MVVM 패턴을 적용시켜 전체적은 구조를 Kotlin으로 구현을 해보자.
본론
구조는 다음과 같다.
- PhotoData_Dao.kt
- PhotoDB
- PhotoViewModel
- PhotoRepositiory
- PhotoData
PhotoData_Dao.kt
DB에 접근해 질의를 수행하는 DAO를 만든다.
다음과 같이 질의와 함께 정의한다.
interface PhotoData_Dao {
@Insert(onConflict = REPLACE)
fun insert(photoData: PhotoData) : Long
@Insert(onConflict = REPLACE)
fun insert(tagData: TagData)
@Update
fun update(photoData: PhotoData)
@Update
fun update(tagData: TagData)
@Query("DELETE FROM photo_data WHERE photo_id = :photo_id")
fun deleteById(photo_id : Long)
@Query("DELETE FROM tag_data WHERE photo_id = :photo_id")
fun deleteTagById(photo_id: Long)
@Query("DELETE FROM tag_data WHERE photo_id = :photo_id AND tag = :tag")
fun delete(photo_id : Long, tag : String)
@Query("SELECT photo_id as thumbnail_path, file_path as data FROM photo_data WHERE photo_id IN (SELECT MAX(photo_id) FROM photo_data GROUP BY file_path) ORDER BY data")
fun getNameDir() : LiveData<List<thumbnailData>>
@Query("SELECT thumbnail_path, location_info as data FROM photo_data WHERE photo_id IN (SELECT MAX(photo_id) FROM photo_data GROUP BY location_info) ORDER BY data")
fun getLocationDir() : LiveData<List<thumbnailData>>
@Query("SELECT tag FROM photo_data, tag_data WHERE date_info BETWEEN :from AND :to AND photo_data.photo_id = tag_data.photo_id GROUP BY tag ORDER BY count(*) LIMIT 1")
fun getDateInfo(from: Date, to : Date) : String
@Query("SELECT thumbnail_path, tag as data FROM photo_data, (SELECT MAX(photo_id) as photo_id, tag FROM tag_data GROUP BY tag) tag_data WHERE photo_data.photo_id = tag_data.photo_id ORDER BY data")
fun getTagDir() : LiveData<List<thumbnailData>>
@Query("SELECT * FROM photo_data where file_path = :name")
fun getNameDir(name : String) : LiveData<List<PhotoData>>
@Query("SELECT * FROM photo_data where location_info = :loc")
fun getLocationDir(loc : String) : LiveData<List<PhotoData>>
@Query("SELECT * FROM photo_data where date_info = :date")
fun getDateDir(date : Int) : LiveData<List<PhotoData>>
@Query("SELECT photo_data.* FROM photo_data, tag_data where (photo_data.photo_id = tag_data.photo_id) AND (tag_data.tag = :tag)")
fun getTagDir(tag : String) : LiveData<List<PhotoData>>
@Query("SELECT tag_data.photo_id, tag_data.tag, tag_data.type FROM photo_data, tag_data WHERE ((photo_data.file_path = :name) AND (photo_data.photo_id = tag_data.photo_id)) ORDER BY tag")
fun getNameTag(name : String) : LiveData<List<TagData>>
@Query("SELECT tag_data.photo_id, tag_data.tag, tag_data.type FROM photo_data, tag_data WHERE ((photo_data.location_info = :loc) AND (photo_data.photo_id = tag_data.photo_id)) ORDER BY tag")
fun getLocationTag(loc : String) : LiveData<List<TagData>>
@Query("SELECT tag_data.photo_id, tag_data.tag, tag_data.type FROM photo_data, tag_data WHERE ((photo_data.date_info = :date) AND (photo_data.photo_id = tag_data.photo_id)) ORDER BY tag")
fun getDateTag(date : Int) : LiveData<List<TagData>>
@Query("SELECT tag_data.photo_id, tag_data.tag, tag_data.type FROM photo_data, tag_data WHERE ((tag_data.tag = :tag) AND (photo_data.photo_id = tag_data.photo_id)) ORDER BY tag")
fun getTagTag(tag : String) : LiveData<List<TagData>>
@Query("SELECT count(*) FROM photo_data")
fun getSize() : Int
}
PhotoDB.kt
데이터베이스를 정의하고 생성한다. DB 모델에 대한 정보가 들어간다.
@Database(entities = [PhotoData::class, TagData::class], version = 1)
@TypeConverters(Converters::class)
abstract class PhotoDB: RoomDatabase() {
abstract fun PhotoData_Dao() : PhotoData_Dao
companion object {
private var INSTANCE : PhotoDB? = null
//singleton patton
fun getInstance(context: Context) : PhotoDB? {
if(INSTANCE == null) {
//synchronized : 중복 방지
synchronized(PhotoDB::class) {
INSTANCE = Room.databaseBuilder(context.applicationContext,
PhotoDB::class.java, "photo.db")
.fallbackToDestructiveMigration()
.build()
}
}
return INSTANCE
}
}
}
PhotoViewModel.kt
뷰 모델을 정의한다. 뷰에서 직접 데이터를 참조하기도 하고, 뷰 모델에서 뷰를 감시하기도 한다.
class PhotoViewModel(application: Application) : AndroidViewModel(application) {
private val repo : PhotoRepository = PhotoRepository(application)
fun Insert(photo : PhotoData) : Long {
return repo.insert(photo)
}
fun Insert(tag : TagData) {
repo.insert(tag)
}
//기본 검색 썸네일
fun getNameDir() : LiveData<List<thumbnailData>> {
return repo.getNameDir()
}
fun getLocationDir() : LiveData<List<thumbnailData>> {
return repo.getLocationDir()
}
fun getDateInfo(from : Date, to : Date) : String? {
return repo.getDateInfo(from, to)
}
fun getTagDir() : LiveData<List<thumbnailData>> {
return repo.getTagDir()
}
//폴더 선택 시
fun getNameDir(name : String) : LiveData<List<PhotoData>> {
return repo.getNameDir(name)
}
fun getLocationDir(loc : String) : LiveData<List<PhotoData>> {
return repo.getLocationDir(loc)
}
fun getDateDir(date : Int) : LiveData<List<PhotoData>> {
return repo.getDateDir(date)
}
fun getTagDir(tag : String) : LiveData<List<PhotoData>> {
return repo.getTagDir(tag)
}
fun getNameTag(name : String) : LiveData<List<TagData>> {
return repo.getNameTag(name)
}
fun getLocationTag(loc : String) : LiveData<List<TagData>> {
return repo.getLocationTag(loc)
}
fun getDateTag(date : Int) : LiveData<List<TagData>> {
return repo.getDateTag(date)
}
fun getTagTag(tag : String) : LiveData<List<TagData>> {
return repo.getTagTag(tag)
}
fun getSize() : Int {
return repo.getSize()
}
}
PhotoRepository.kt
뷰에서 데이터를 참조할 떄, 뷰 모델을 먼저 참조하고 뷰 모델이 리파지터리를 통해 데이터를 가져오도록 한다.
class PhotoRepository(application: Application) {
val photoDao : PhotoData_Dao
companion object {
private class insertPhotoAsyncTask constructor(private val asyncTask: PhotoData_Dao) : AsyncTask<PhotoData, Void, Long>() {
override fun doInBackground(vararg params: PhotoData?): Long? {
return asyncTask.insert(params[0]!!)
}
}
private class insertTagAsyncTask constructor(private val asyncTask: PhotoData_Dao) : AsyncTask<TagData, Void, Void>() {
override fun doInBackground(vararg params: TagData?): Void? {
asyncTask.insert(params[0]!!)
return null
}
}
private class getDateTagAsyncTask constructor(private val asyncTask: PhotoData_Dao) : AsyncTask<Date, Void, String>() {
override fun doInBackground(vararg params: Date?): String? {
return asyncTask.getDateInfo(params[0]!!, params[1]!!)
}
}
private class getSizeAsyncTask constructor(private val asyncTask: PhotoData_Dao) : AsyncTask<Void, Void, Int>() {
override fun doInBackground(vararg params: Void?): Int {
return asyncTask.getSize()
}
}
}
init {
val db = PhotoDB.getInstance(application)!!
photoDao = db.PhotoData_Dao()
}
fun insert(photo : PhotoData) : Long {
return insertPhotoAsyncTask(photoDao).execute(photo).get()
}
fun insert(tag : TagData) {
insertTagAsyncTask(photoDao).execute(tag)
}
fun getNameDir() : LiveData<List<thumbnailData>> {
return photoDao.getNameDir()
}
fun getLocationDir() : LiveData<List<thumbnailData>> {
return photoDao.getLocationDir()
}
fun getDateInfo(from : Date, to : Date) : String? {
return getDateTagAsyncTask(photoDao).execute(from, to).get()
}
fun getTagDir() : LiveData<List<thumbnailData>> {
return photoDao.getTagDir()
}
fun getNameDir(name : String) : LiveData<List<PhotoData>> {
return photoDao.getNameDir(name)
}
fun getLocationDir(loc : String) : LiveData<List<PhotoData>> {
return photoDao.getLocationDir(loc)
}
fun getDateDir(date : Int) : LiveData<List<PhotoData>> {
return photoDao.getDateDir(date)
}
fun getTagDir(tag : String) : LiveData<List<PhotoData>> {
return photoDao.getTagDir(tag)
}
fun getNameTag(name : String) : LiveData<List<TagData>> {
return photoDao.getNameTag(name)
}
fun getLocationTag(loc : String) : LiveData<List<TagData>> {
return photoDao.getLocationTag(loc)
}
fun getDateTag(date : Int) : LiveData<List<TagData>> {
return photoDao.getDateTag(date)
}
fun getTagTag(tag : String) : LiveData<List<TagData>> {
return photoDao.getTagTag(tag)
}
fun getSize() : Int {
return getSizeAsyncTask(photoDao).execute().get()
}
}
PhotoData.kt
DB 엔터티나 기타 데이터를 생성하고 정의한다.
@Entity(tableName = "photo_data")
class PhotoData(@PrimaryKey(autoGenerate = true) var photo_id: Long,
@ColumnInfo(name = "name") var name : String,
@ColumnInfo(name = "file_path") var file_path : String,
@ColumnInfo(name = "thumbnail_path") var thumbnail_path : String,
@ColumnInfo(name = "location_info") var location_info : String?,
@ColumnInfo(name = "date_info") var date_info : Date?,
@ColumnInfo(name = "favorite") var favorite : Boolean)
@Entity(tableName = "tag_data",
primaryKeys = ["photo_id", "tag"],
foreignKeys = [ForeignKey(entity = PhotoData::class,
parentColumns = arrayOf("photo_id"),
childColumns = arrayOf("photo_id")
)]
)
class TagData(var photo_id: Long,
var tag : String,
@ColumnInfo var type : String)
data class thumbnailData( var thumbnail_path: String,
var data : String )
연결
다음과 같이 뷰에서 뷰 모델과 연결한다.
var vm = ViewModelProviders.of(this).get(PhotoViewModel::class.java)
vm.getTagDir().observe(this,
Observer<List<PhotoData>> { t -> PhotoList =
recyclerAdapter?.setThumbnailList(t)!!
})
결론
이렇게 MVVM 패턴을 구축할 수 있다.
DB에 대한 클래스와 MVVM 구조만 잘 정의해놓으면 실제로 액티비티에서는 위의 예제와 같이 단순히 뷰 모델과 연결함으로써 추가적인 번거로움 없이 편하게 관리할 수 있다.
향후과제
-
참고자료
https://wonsohana.wordpress.com/2019/05/14/android-mvvm-%EC%9D%84-%EC%A0%95%EB%A6%AC%ED%95%B4%EB%B3%B4%EC%9E%90/
https://blog.yena.io/studynote/2019/03/16/Android-MVVM-AAC-1.html
Writer: Jae-Hwan Lee
댓글남기기