Retrofit moshi coroutines Room LiveData
网络请求 moshi 数据解析 协程里请求数据 同步本地数据库 更新UI
ext{
kotlin_version = '1.3.71'
version_lifecycle_extensions = "2.2.0"
version_lifecycle = "2.2.0"
version_room = "2.2.5"
version_core = "1.3.2"
version_coroutine = "1.3.6"
version_navigation = '1.0.0'
version_constraint_layout = "2.0.0-rc1"
version_retrofit_coroutines_adapter = "0.9.2"
version_joda = "2.10"
version_retrofit = "2.5.0"
version_work = "1.0.1"
version_moshi = "1.8.0"
}
// Coroutines for getting off the UI thread
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$version_core"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$version_coroutine"
// Retrofit for networking
implementation "com.squareup.retrofit2:retrofit:$version_retrofit"
implementation "com.squareup.retrofit2:converter-moshi:$version_retrofit"
implementation "com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:$version_retrofit_coroutines_adapter"
// Joda time library for dealing with time
implementation "joda-time:joda-time:$version_joda"
// ViewModel and LiveData (arch components)
implementation "androidx.lifecycle:lifecycle-extensions:$version_lifecycle_extensions"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$version_lifecycle"
- Retrofit 服务搭建
interface DevbyteService {
@GET("xxx.json")
fun getPlaylist(): Deferred<NetworkVideoContainer>
}
/**
* Build the Moshi object that Retrofit will be using, making sure to add the Kotlin adapter for
* full Kotlin compatibility.
*/
private val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
/**
* Main entry point for network access. Call like `Network.devbytes.getPlaylist()`
*/
object Network {
// Configure retrofit to parse JSON and use coroutines
private val retrofit = Retrofit.Builder()
.baseUrl("https://xxx")
.addConverterFactory(MoshiConverterFactory.create(moshi))
.addCallAdapterFactory(CoroutineCallAdapterFactory())
.build()
val devbytes = retrofit.create(DevbyteService::class.java)
}
2.Room本地数据库
/**
* Videos represent a devbyte that can be played.
*/
data class Video(val title: String,
val description: String,
val url: String,
val updated: String,
val thumbnail: String) {
/**
* Short description is used for displaying truncated descriptions in the UI
*/
val shortDescription: String
get() = description.smartTruncate(200)
}
@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<Video> {
return map {
Video(
url = it.url,
title = it.title,
description = it.description,
updated = it.updated,
thumbnail = it.thumbnail)
}
}
@Dao
interface VideoDao {
@Query("select * from databasevideo")
fun getVideos(): LiveData<List<DatabaseVideo>>
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertAll(vararg videos: 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
}
3.接收数据的对象
/**
* 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?)
fun NetworkVideoContainer.asDatabaseModel(): Array<DatabaseVideo> {
return videos.map {
DatabaseVideo(
title = it.title,
description = it.description,
url = it.url,
updated = it.updated,
thumbnail = it.thumbnail)
}.toTypedArray()
}
4.在ViewModel中使用
class DevByteViewModel(application: Application) : AndroidViewModel(application) {
private val database = getDatabase(application)
/**
* init{} is called immediately when this ViewModel is created.
*/
init {
viewModelScope.launch {
refreshVideos()
}
}
val playlist : LiveData<List<Video>> =
Transformations.map(database.videoDao.getVideos()) {
it.asDomainModel()
}
private suspend fun refreshVideos(){
withContext(Dispatchers.IO) {
val playlist = Network.devbytes.getPlaylist().await()
database.videoDao.insertAll(*playlist.asDatabaseModel())
}
}
/**
* Factory for constructing DevByteViewModel with parameter
*/
class Factory(val app: Application) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(DevByteViewModel::class.java)) {
@Suppress("UNCHECKED_CAST")
return DevByteViewModel(app) as T
}
throw IllegalArgumentException("Unable to construct viewmodel")
}
}
}
5.在页面上使用数据
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.playlist.observe(viewLifecycleOwner, Observer<List<Video>> { videos ->
videos?.apply {
viewModelAdapter?.videos = videos
}
})
}
这里介绍下moshi的简单用法
1.JSON 转对象
val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
val type =
Types.newParameterizedType(List::class.java, Video::class.java)
val adapter:JsonAdapter<List<Video>> = moshi.adapter(type)
val fromJson = adapter.fromJson(value)
2.对象转JSON
val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
val type =
Types.newParameterizedType(List::class.java, Video::class.java)
val adapter:JsonAdapter<List<Video>> = moshi.adapter(type)
adapter.toJson(list)