버그 잡이

[Udacity android with kotlin] 6. Room #ViewModel+LiveData+Room 본문

모던 안드로이드/Udacity Android with kotlin

[Udacity android with kotlin] 6. Room #ViewModel+LiveData+Room

버그잡이 2020. 4. 14. 12:08

Room이란?

 

The Room persistence Library provides an abstraction layer over SQLite

 

- SQLite를 편하게 쓸 수 있게 도와주는 라이브러리다.

- 일반적으로 내부DB의 개념으로 활용된다.

 

 

용어 정리

 

Room을 이해하기 위해서는 다음 용어를 이해할 필요가 있다.

 

1. Entity

 

Table row로 table에 저장되는 한 행의 자료이다.

ex) 하룻밤 수면 데이터

 

2. Query

 

"request for data or information from DB table"

DB테이블에 대해서 데이터나 정보를 요청하는 행위다.

 

3. Annotation

 

DB와 연결되는 interface다.

ex) @PrimaryKey라는 annotation을 달아주면 해당 칼럼을 db에서 primarykey로 인식할 수 있게 도와준다.

 

data.class

 

필요한 클래스

 

1. DATA

- table을 만드는 클래스이다. 

- 위 사진과 같은 형태를 취한다.

 

2. DAO

- Query 작업을 위한 interface

 

@Dao
interface SleepDatabaseDao{

    @Insert
    fun insert(night: SleepNight)

    @Update
    fun update(night: SleepNight)

    @Query("SELECT * FROM daily_sleep_quality_table where nightId = :key")
    fun get(key: Long):SleepNight

    @Query("DELETE FROM daily_sleep_quality_table")
    fun clear()

    @Query("SELECT * from daily_sleep_quality_table order by nightId DESC")
    fun getAllNight(): LiveData<List<SleepNight>>

    @Query("SELECT * FROM daily_sleep_quality_table ORDER BY nightId DESC LIMIT 1")
    fun getTonight(): SleepNight?

}

 

*LiveData & Room

- getAllNight()를 보면 반환 값이 LiveData이다.

- 이를 뷰모델에 변수로 가져오면 LiveData로써 기능을 수행할 수 있다.

- 예를 들어, 뷰모델에 var allNight = getAllNight() 이라는 변수가 있다고 해보자. 이는 현재 livedata이다.

- 따라서, activity에서 viewmodel.night.observe()를 통해서 해당 data에 대한 변화를 감지하고 최신화 할 수 있다.

 

 

 

3. DB생성

- DB가 있어야 그 안에서 table도 생성하고 query도 할 수 있다.

- 아래와 같이 singleton 패턴으로 생성

 

@Database(entities = [SleepNight::class], version = 1, exportSchema = false)
abstract class SleepDatabase : RoomDatabase(){

    abstract val sleepDatabaseDao: SleepDatabaseDao

    //singleton 패턴으로 객체 생성 없이 사용할 수 있게 한다.
    companion object{

        @Volatile   //up-to-date same to all execute thread. 하나의 thread에서 최신화 되면 나머지 스레드에서도 최신화가 된다(?)
        private var INSTANCE: SleepDatabase? = null

        fun getInstance(context: Context) : SleepDatabase{

            synchronized(this){     //이것도 여러개의 스레드에서 데이터를 최신화해주는 역할
                var instance = INSTANCE

                //instance가 null인 경우 db 생성
                if(instance == null){
                    instance = Room.databaseBuilder(
                            context.applicationContext,
                            SleepDatabase::class.java,
                            "sleep_history_database"
                    )
                            .fallbackToDestructiveMigration()
                            .build()
                    INSTANCE = instance
                }

                return instance
            }
        }
    }
}

 

 

 

ViewModel

 

자 이제 viewmodel에서 db 관련 Query를 작업해보자.

 

1. ViewModel이 아닌 AndroidViewModel을 상속받는다.

- singleton 패턴의 DB를 받기 위해서는 context가 필요한데 AndroidViewModel을 상속받으면 application context를 받을 수 있다.

 

2. DB instance를 가져온 후 Query 작업을 위한 dao를 불러온다.

- val database = SleepDatabase.getInstance(application).sleepDatabaseDao

 

3. Dao를 활용하여 각종 작업을 진행한다.

- insert, update, geTonight 등..

 

class TestViewModel(application: Application) : AndroidViewModel(application) {

    val database = SleepDatabase.getInstance(application).sleepDatabaseDao

    private var tonight = MutableLiveData<SleepNight>()

    init {
        tonight.value = getTonightFromDatabase()

    }


    //시간 측정중이면 시간시간 데이터 받아오기
    private fun getTonightFromDatabase(): SleepNight? {

        var night = database.getTonight()   //가장 최근 수면 기록 row를 가져온다.
        if (night?.endTimeMilli != night?.startTimeMilli) {   //수면이 측정중이지 않은 경우 반환할 데이터가 없다,
            night = null
        }
        return night   //수면을 측정 중인 경우는 시작 시간 반환
    }


    //시작 버튼 클릭시 자료 insert
    fun startCount(){
        val night = SleepNight()
        database.insert(night)

        //시작 시간도 최신화
        tonight.value = getTonightFromDatabase()
    }


    //종료 버튼 클릭시 자료 update
    fun stopCount(){
        val targetNight = tonight.value
        targetNight!!.endTimeMilli = System.currentTimeMillis()

        database.update(targetNight)

        //시작 시간도 최신화
        tonight.value = getTonightFromDatabase()

    }

}

 

 

+코루틴

 

코루틴을 사용하면 Room을 코틀린 답게 사용할 수 있다.

 

앱 동작시 다양한 스레드가 동시에 일을 하는데 중요한 것은 서브 스레드가 메인 스레드를 방해할 경우 화면 버벅임이나 앱 강제 종료 등의 문제를 야기할 수 있다는 것이다. 코루틴은 이러한 문제 해결을 도와준다.

 

코루틴에는 더 깊은 개념과 다양한 기능들이 있지만 여기서는 DB 작업이 메인스레드를 방해하지 않게 도와주는 코루틴에 대해서 알아보겠다.

 

 

코루틴이란

 

caller가 함수를 call하고, 함수가 caller에게 값을 return 하면서 종료하는 것에 더해

return 하는 대신 suspend하면 나중에 메인 스레드가 resume하여 중단된 지점부터 실행을 이어갈 수 있다.

 

 

코루틴 용어 정리

 

CoroutineScope

 

코루틴의 범위, 코루틴 블록을 묶음으로 제어할 수 있는 단위

 

CoroutineContext

 

코루틴을 어떻게 처리할 것인지에 대한 여러가지 정보의 집합

주요 요소로는 Job 과 Dispatcher가 있다.

 

Dispacher

 

어떤 스레드를 이용해서 어떻게 동작할 것인지 결정

출처 : https://medium.com/@limgyumin/%EC%BD%94%ED%8B%80%EB%A6%B0-%EC%BD%94%EB%A3%A8%ED%8B%B4%EC%9D%98-%EA%B8%B0%EC%B4%88-cac60d4d621b

 

 

코루틴 사용법

1.  사용할 Dispatcher를 결정

 

2.  Dispatcher를 이용해서 CoroutineScope 만들고

 

3.  coroutineScope의 launch 또는 async에 수행할 코드를 작성한다.

 

*launch : 반환값이 없는 Job 객체

*async : 반환값이 있는 deffered 객체 / 마지막 구문의 실행 결과가 반환됨

 


private var viewModelJob = Job()    //이를 통해 자식 코루틴을 한번에 제어할 수 있

override fun onCleared() {
     super.onCleared()
     //viewmodel이 죽으면 코루틴도 죽이겠다.
     viewModelJob.cancel()
}

//dispatcher 설정
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)


//UI관련 작업은 Main dispatcher에서
fun onStartTracking(){
       uiScope.launch {
           val newNight = SleepNight()

           insert(newNight)    //db에 새로운 row 추가

           tonight.value = getTonightFromDatabase()    
       }
   }
    
//db 관련 작업은 IO dispatcher에서
private suspend fun insert(night: SleepNight){
    withContext(Dispatchers.IO){
        database.insert(night)
    }
}

 

*job

코드를 보면 job이라는 객체가 있는 것을 확인할 수 있다.

 

job은 코루틴 블록 객체이다. 이를 통해서 코루틴을 제어할 수 있다.

보통 launch를 통해서 반환되는데 위와 같이 여러개의 launch에 적용하고 싶으면 코루틴 스코프에 추가한다.

위의 코드에서는 viewmodel이 종료될시 코루틴도 종료되도록 코루틴을 제어한 것이다.

 

*withContext

현재 코루틴 컨택스트를 변경한다.

위 코드에서 insert()는 db 작업이기 때문에 이에 적합한 IO 스레드에서 실행시켜주는 것이다.

 

 

이번에 코루틴은 각 단어에 대한 이해 수준이었다.

추후 더 공부하여 심화된 내용으로 정리해봐야겠다.

 

 

 

 

느낀점

 

1. 아직 이해가 부족하다.

- 코루틴은 갈길이 한참 멀고

- room+liveData의 연동도 뭔가 명확하게 다가오지 않는다.

 

-> recyclerView까지 듣고 나만의 TodoList앱을 만들어봐야겠다. (직접 손으로 짜보면 나아지겠지.)

 

 

 

*참고 자료

https://medium.com/@jooyunghan/%EC%BD%94%EB%A3%A8%ED%8B%B4-%EC%86%8C%EA%B0%9C-504cecc89407

 

https://medium.com/@limgyumin/%EC%BD%94%ED%8B%80%EB%A6%B0-%EC%BD%94%EB%A3%A8%ED%8B%B4%EC%9D%98-%EA%B8%B0%EC%B4%88-cac60d4d621b

 

https://thdev.tech/kotlin/2019/04/08/Init-Coroutines-Job/

 

https://developer.android.com/topic/libraries/architecture/room

 

 

 

반응형
Comments