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
캐시란, 다음에 곧 쓸 데이터를 로컬에 저장해뒀다가 필요할 때 다시 쓰는 것
우리앱에서 레포지토리 패턴을 써야 하는 이유
- 앱 실행 시에 다운로드받아 캐시해 사용할 만한 데이터들이 있다. 필터 목록 같은 데이터. 이 데이터를 따로 관리하는 클래스를 두는 것보다, 레포지토리 패턴을 써서 레포지토리 안에서 필요한 데이터만 캐시 시키면 레포지토리를 쓰는 곳에서는 캐시되는 데이터인지 실시간 데이터인지 알 필요 없어서 관심사의 분리가 된다.
- 네트워크 서비스를 대신해서 테스트할 수 있는 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를 사용한 프리패칭까지!