Udacity Developing Android Apps with Kotlin 레슨6 App Architecture (Persistence) 요약

Lesson 6: App Architecture (Persistence)

1. Wake Up, Aleks!

2. Introduction

  • Room Database
  • DAO
  • Application Architecture with Room
  • Coroutines

Room


3. SQLite Primer

SQLite 문법에 대한 간단 설명

4. Designing Entities

Room DB layer 구조

5. Exercise: Creating the SleepNight Entity

data class를 사용해 Entity 클래스 만들기

6. Data Access Object (DAO)

DAO 어노테이션의 종류

7. Exercise: DAO - SleepDatabaseDao

DAO 클래스 만들기

8. Creating a Room Database

Room DB를 만드는 순서

9. Exercise: Creating a Room Database

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

abstract val sleepDatabaseDao: SleepDatabaseDao

companion object {

    @Volatile
    private var INSTANCE: SleepDatabase? = null

    fun getInstance(context: Context): SleepDatabase {
        synchronized(this) {
            var instance = INSTANCE 

            if (instance == null) {
                instance = Room.databaseBuilder(
                        context.applicationContext,
                        SleepDatabase::class.java,
                        "sleep_history_database"
                )
                        .fallbackToDestructiveMigration()
                        .build()
                INSTANCE = instance
            }
            return instance
        }
     }
  }
}
  • exportScheme는 기본이 true이고, true일 때 스키마파일을 폴더에 저장시킨다고 한다.
  • @Volatile의 의미 : https://woovictory.github.io/2019/01/04/Android-What-is-Singleton-Pattern
    • synchronized 블록으로 감쌌더라도 INSTANCE의 null값이 스레드 캐시에 저장돼있는 상태라면, 다른 스레드에서 먼저 INSTANCE에 DB인스턴스를 대입했더라도 스레드 캐시에 있는 null값을 사용할 확률이 있다. INSTANCE를 volatile로 선언해서 항상 메인 메모리에서 읽어오도록 하면 스레드 캐시에 저장했을 때 발생하는 문제를 예방할 수 있다.
  • .fallbackToDestructiveMigration() 옵션은 DB 버전이 올라갔을 때 DB 데이터를 제거하고 DB를 새로 만든다.

10. Testing the Room Database

@RunWith(AndroidJUnit4::class)
class SleepDatabaseTest {ㅇ

    private lateinit var sleepDao: SleepDatabaseDao
    private lateinit var db: SleepDatabase

    @Before
    fun createDb() {
        val context = InstrumentationRegistry.getInstrumentation().targetContext
        // Using an in-memory database because the information stored here disappears when the
        // process is killed.
        db = Room.inMemoryDatabaseBuilder(context, SleepDatabase::class.java)
                // Allowing main thread queries, just for testing.
                .allowMainThreadQueries()
                .build()
        sleepDao = db.sleepDatabaseDao
    }

    @After
    @Throws(IOException::class)
    fun closeDb() {
        db.close()
    }

    @Test
    @Throws(Exception::class)
    fun insertAndGetNight() {
        val night = SleepNight()
        sleepDao.insert(night)
        val tonight = sleepDao.getTonight()
        assertEquals(tonight?.sleepQuality, -1)
    }
}

Room 유닛테스트는 쿼리 테스트용으로 좋을 것 같다.

11. Displaying Sleep Data

메인 액티비티 레이아웃과 두 프래그먼트 레이아웃을 살펴봄.

태그 : 는 루트 레이아웃이 꼭 있어야 하는데, 는 루트 레이아웃이 필요 없다. 다른 레이아웃에 삽입될 때 태그는 사라지고 자식태그들만 붙여진다.

12. Adding A ViewModel

비지니스 로직은 ViewModel로 구현하고, DB 명령은 coroutines를 사용해 백그라운드 스레드로 실행할 것이라고 함.

13. Exercise: Adding a ViewModel

ViewModel과 ViewModelFactory를 구현하고 ViewModel을 레이아웃 파일에 연결함.

Coroutines


14. Multithreading and Coroutines

코루틴의 특징

코루틴을 쓰려면 필요한 것들

https://twitter.com/akarnokd/status/979732723152687106

15. Exercise: Coroutines for Long-running Operations

private var viewModelJob = Job() // 이 ViewModel의 모든 코루틴을 취소시킬 수 있는 객체

Scope 개념 잘 모르겠음!

Room Dao로 반환한 LiveData는 Room DB 데이터가 바뀌면 자동으로 갱신되는 장점이 있다!

16. Googler Interview: Florina Muntenescu

17. Navigation and Sleep Quality

18. Exercise: Recording Sleep Quality

sleepTrackerViewModel.navigateToSleepQuality.observe(this, Observer { night ->
    night?.let {
        this.findNavController().navigate(
                SleepTrackerFragmentDirections
                        .actionSleepTrackerFragmentToSleepQualityFragment(night.nightId))
        // Reset state to make sure we only navigate once, even if the device
        // has a configuration change.
        sleepTrackerViewModel.doneNavigating()
    }
})

state가 아닌 event는 이런 식으로 View(Fragment)단에서 LiveData를 observe 해서 처리하는 패턴이 보임.

SleepQualityFragment에서 DB 데이터를 바꾸고 SleepTrackerFragment로 돌아왔을 때 SleepTrackerFragment에서 사용하는 LiveData도 자동으로 갱신된 것을 볼 수 있다. 심지어 라이프사이클을 잘 알기 때문에 자동으로 적절한 시점에 갱신 된다. 화면 간 상태 동기화 문제를 Bus 기법이나 다른 보일러플레이트 코드 없이도 간단하게 해결할 수 있는 장점이 있다. 단, 상태 동기화가 필요한 데이터의 DB화하는 또 다른 수고가 생긴다.

19. Transformation Maps

20. Exercise: Button States and SnackBar

val stopButtonVisible = Transformations.map(tonight) {
	null != it
}

val clearButtonVisible = Transformations.map(nights) {
	it?.isNotEmpty()
}

LiveData 가공은 Transformations.map()! 다시 한 번 각인됨.

21. Where to go next?

ViewModelFactory를 쓰는 이유 하나 더 생각 남 : DI를 쓰면 코드 수정을 최소화해서 테스트하기 편하기 때문

마치면서

다른 화면 사이에서 Room~LiveData 자동 갱신 연계가 매우 인상적이었다. 정말 간결하고 쓰기 편하다고 생각한다.