Udacity Developing Android Apps with Kotlin 레슨9 Behind the scenes 요약

Lesson 9: Behind the scenes

1. Offline Movie Night

2. Exercise: Introduction

offline caching에 대해 배울 거야!

3. What’s in a Cache

캐시란, 다음에 곧 쓸 데이터를 로컬에 저장해뒀다가 필요할 때 다시 쓰는 것

우리앱에서 레포지토리 패턴을 써야 하는 이유

  1. 앱 실행 시에 다운로드받아 캐시해 사용할 만한 데이터들이 있다. 필터 목록 같은 데이터. 이 데이터를 따로 관리하는 클래스를 두는 것보다, 레포지토리 패턴을 써서 레포지토리 안에서 필요한 데이터만 캐시 시키면 레포지토리를 쓰는 곳에서는 캐시되는 데이터인지 실시간 데이터인지 알 필요 없어서 관심사의 분리가 된다.
  2. 네트워크 서비스를 대신해서 테스트할 수 있는 mock을 교체하기 쉽다.

캐시 인벨리데이션에 대해 생각해야 한다.

구조화된 데이터를 캐시하기에 적절한 Room DB

작은 데이터를 캐시하기에 적절한 SharedPreferences

이미지나 데이터파일을 캐시하기에 적절한 파일

4. How to store data

네트워크 → Application Code → Database → Notify Application Code → UI

5. Decorating a Room

VideoDao

Room 메소드 종류

6. Building a Room

domain object(database) / DTO(network)

7. Exercise: Add a DatabaseVideo Entity

fun List<DatabaseVideo>.asDomainModel(): List<Video> {
// DO to DTO
    return map {
        Video (
           url = it.url,
           title = it.title,
           description = it.description,
           updated = it.updated,
           thumbnail = it.thumbnail)
    }
}

fun NetworkVideoContainer.asDatabaseModel(): Array<DatabaseVideo> {
// DTO to DO
    return videos.map {
        DatabaseVideo (
           title = it.title,
           description = it.description,
           url = it.url,
           updated = it.updated,
           thumbnail = it.thumbnail)
    }.toTypedArray()
}

8. Exercise: Add the VideoDao

9. Exercise: Refactor the VideoDao

@Dao
interface VideoDao {
    @Query("select * from databasevideo")
    fun getVideos(): LiveData<List<DatabaseVideo>>

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insertAll(vararg videos: DatabaseVideo)
}

10. Exercise: Add the VideosDatabase

11. Room Review

12. Googler Interview: Sumir Kataria

work manager

13. Using a Room

repository pattern: separation of concerns

14. Exercise: Build a Repository

class VideosRepository(private val database: VideosDatabase) {

   val videos: LiveData<List<Video>> = Transformations.map(database.videoDao.getVideos()) {
      it.asDomainModel()
   }

   suspend fun refreshVideos() {
       withContext(Dispatchers.IO) {
           val playlist = Network.devbytes.getPlaylist().await()
           database.videoDao.insertAll(*playlist.asDatabaseModel())
					// *playlist : Array를 vararg로 사용할 수 있게 하는 연산자
       }
   }
}

15. Exercise: Use the Repository

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

    private val viewModelJob = SupervisorJob()
    private val viewModelScope = CoroutineScope(viewModelJob + Dispatchers.Main)

    private val database = getDatabase(application)
    private val videosRepository = VideosRepository(database)

    init {
        viewModelScope.launch {
            videosRepository.refreshVideos()
        }
    }

    val playlist = videosRepository.videos

    override fun onCleared() {
        super.onCleared()
        viewModelJob.cancel()
    }
}

16. Video: Offline Caching Review

17. WorkManager for the background

백그라운드 작업의 종류

WorkManager의 특징

WorkManager의 실행환경 제약들

Pre-fetching 가이드

18. Exercise: Create a Worker

19. Exercise: Schedule Background Work

Appliaction.onCreate 에서 너무 많은 작업을 하면 앱 실행 시 렉이 길어진다! 그래서 이 프로젝트에선 아래와 같이 코딩했음.

private fun delayedInit() {
    applicationScope.launch {
        setupRecurringWork()
    }
}

private fun setupRecurringWork() {
    val constraints = Constraints.Builder()
            .setRequiredNetworkType(NetworkType.UNMETERED)
            .setRequiresBatteryNotLow(true)
            .setRequiresCharging(true)
	            .apply {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                    setRequiresDeviceIdle(true)
                }
            }.build()

    val repeatingRequest
            = PeriodicWorkRequestBuilder<RefreshDataWorker>(1, TimeUnit.DAYS)
            .setConstraints(constraints)
            .build()

    WorkManager.getInstance().enqueueUniquePeriodicWork(
            RefreshDataWorker.WORK_NAME,
            ExistingPeriodicWorkPolicy.KEEP,
            repeatingRequest)
}

20. Wrapping up Caching

마치면서

Repository.getData → save in database → auto-notified to Repository.LiveData → ViewModel has Repository.LiveData → Fragment has observed ViewModel.LiveData

매우 깔끔한 캐싱 전략! 심지어

WorkManager를 사용한 프리패칭까지!