Android Kotlin Fundamentals Course 코드랩 하면서 노트. Android Kotlin Fundamentals
Starter code
/**
* VideoHolder holds a list of Videos.
*
* This is to parse first level of our network result which looks like
*
* {
* "videos": []
* }
*/
@JsonClass(generateAdapter = true)
data class NetworkVideoContainer(val videos: List<NetworkVideo>)
/**
* Videos represent a devbyte that can be played.
*/
@JsonClass(generateAdapter = true)
data class NetworkVideo(
val title: String,
val description: String,
val url: String,
val updated: String,
val thumbnail: String,
val closedCaptions: String?)
/**
* Convert Network results to database objects
*/
fun NetworkVideoContainer.asDomainModel(): List<DevByteVideo> {
return videos.map {
DevByteVideo(
title = it.title,
description = it.description,
url = it.url,
updated = it.updated,
thumbnail = it.thumbnail)
}
}
Add an offline cache using Room
Add a dependency.
// build.gradle (Module:app)
// Room dependency
def room_version = "2.1.0-alpha06"
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
Add a DB entity.
// DatabaseEntities.kt
@Entity
data class DatabaseVideo constructor(
@PrimaryKey
val url: String,
val updated: String,
val title: String,
val description: String,
val thumbnail: String
)
fun List<DatabaseVideo>.asDomainModel(): List<DevByteVideo> {
return map {
DevByteVideo(
url = it.url,
title = it.title,
description = it.description,
updated = it.updated,
thumbnail = it.thumbnail
)
}
}
Update a data transfer object.
// DataTransferObjects.kt
/**
* Convert Network results to database objects
*/
fun NetworkVideoContainer.asDomainModel(): List<DatabaseVideo> {
return videos.map {
DatabaseVideo(
title = it.title,
description = it.description,
url = it.url,
updated = it.updated,
thumbnail = it.thumbnail)
}
}
Add VideoDao.
// Room.kt
@Dao
interface VideoDao {
@Query("select * from databasevideo")
fun getVideos(): LiveData<List<DatabaseVideo>>
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertAll(videos: List<DatabaseVideo>)
}
@Database(entities = [DatabaseVideo::class], version = 1)
abstract class VideosDatabase : RoomDatabase() {
abstract val videoDao: VideoDao
}
private lateinit var INSTANCE: VideosDatabase
fun getDatabase(context: Context): VideosDatabase {
synchronized(VideosDatabase::class.java) {
if (!::INSTANCE.isInitialized) {
INSTANCE = Room.databaseBuilder(context.applicationContext,
VideosDatabase::class.java, "videos")
.build()
}
}
return INSTANCE
}
Repository
A repository module handles data operations and allows you to use multiple backends.
// VideosRepository.kt
class VideosRepository(private val database: VideosDatabase) {
val videos: LiveData<List<DevByteVideo>> = Transformations.map(database.videoDao.getVideos()) {
it.asDomainModel()
}
suspend fun refreshVideos() {
withContext(Dispatchers.IO) {
Timber.d("Refresh videos is called");
val playlist = DevByteNetwork.devbytes.getPlaylist()
database.videoDao.insertAll(playlist.asDomainModel())
}
}
}
Update the view model to apply a new refresh strategy.
// DevByteViewModel.kt
class DevByteViewModel(application: Application) : AndroidViewModel(application) {
// init the repository
private val videosRepository = VideosRepository(getDatabase(application))
// ...
// Replace playlist
val playlist = videosRepository.videos
// ...
init {
refreshDataFromRepository()
}
/**
* Refresh data from network and pass it via LiveData. Use a coroutine launch to get to
* background thread.
*/
private fun refreshDataFromRepository() {
viewModelScope.launch {
try {
videosRepository.refreshVideos()
_eventNetworkError.value = false
_isNetworkErrorShown.value = false
} catch (networkError: IOException) {
// Show a Toast error message and hide the progress bar.
if (playlist.value.isNullOrEmpty())
_eventNetworkError.value = true
}
}
}
}
다음 챕터: Android Kotlin Fundamentals