场景:经过网络恳求回来的数据来更新View
先抛出一个很常见的问题:如何经过网络恳求的数据来更新View?这个问题的解决方案是很明确的:
- 在本地缓存/数据库,中查找是否有缓存,假如有缓存直接更新View。
- 假如没有找到缓存,建议网络恳求。
- 网络恳求回来成果后,更新缓存,更新View。
NetworkBoundResource 是什么
NetworkBoundResource是一种Android Jetpack架构组件中的设计形式,完结的就是上述的数据请过过程。由于View的改写依赖于网络恳求的回来的成果,而网络恳求又需求必定的时间,整个过程是一个同步操作,代码中往往也需求传递callback,来完结View的更新。NetworkBoundResource 经过引入LiveData,把数据的恳求变成了一个异步的操作,View的更新经过LiveData 的 observe 来完结。
上图中能够看到,Repository 向外抛出去了一个LiveData,等到拿到了数据后,直接更新LiveData。外部只需求监听LiveData,就能够在数据更新时候来更新View.
NetworkBoundResource 源码如下:
package com.android.example.github.repository
import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
import androidx.annotation.MainThread
import androidx.annotation.WorkerThread
import com.android.example.github.AppExecutors
import com.android.example.github.api.ApiEmptyResponse
import com.android.example.github.api.ApiErrorResponse
import com.android.example.github.api.ApiResponse
import com.android.example.github.api.ApiSuccessResponse
import com.android.example.github.vo.Resource
abstract class NetworkBoundResource<ResultType, RequestType>
@MainThread constructor(private val appExecutors: AppExecutors) {
private val result = MediatorLiveData<Resource<ResultType>>()
init {
result.value = Resource.loading(null)
@Suppress("LeakingThis")
val dbSource = loadFromDb()
result.addSource(dbSource) { data ->
result.removeSource(dbSource)
if (shouldFetch(data)) {
fetchFromNetwork(dbSource)
} else {
result.addSource(dbSource) { newData ->
setValue(Resource.success(newData))
}
}
}
}
@MainThread
private fun setValue(newValue: Resource<ResultType>) {
if (result.value != newValue) {
result.value = newValue
}
}
private fun fetchFromNetwork(dbSource: LiveData<ResultType>) {
val apiResponse = createCall()
// we re-attach dbSource as a new source, it will dispatch its latest value quickly
result.addSource(dbSource) { newData ->
setValue(Resource.loading(newData))
}
result.addSource(apiResponse) { response ->
result.removeSource(apiResponse)
result.removeSource(dbSource)
when (response) {
is ApiSuccessResponse -> {
appExecutors.diskIO().execute {
saveCallResult(processResponse(response))
appExecutors.mainThread().execute {
// we specially request a new live data,
// otherwise we will get immediately last cached value,
// which may not be updated with latest results received from network.
result.addSource(loadFromDb()) { newData ->
setValue(Resource.success(newData))
}
}
}
}
is ApiEmptyResponse -> {
appExecutors.mainThread().execute {
// reload from disk whatever we had
result.addSource(loadFromDb()) { newData ->
setValue(Resource.success(newData))
}
}
}
is ApiErrorResponse -> {
onFetchFailed()
result.addSource(dbSource) { newData ->
setValue(Resource.error(response.errorMessage, newData))
}
}
}
}
}
protected open fun onFetchFailed() {}
fun asLiveData() = result as LiveData<Resource<ResultType>>
@WorkerThread
protected open fun processResponse(response: ApiSuccessResponse<RequestType>) = response.body
@WorkerThread
protected abstract fun saveCallResult(item: RequestType)
@MainThread
protected abstract fun shouldFetch(data: ResultType?): Boolean
@MainThread
protected abstract fun loadFromDb(): LiveData<ResultType>
@MainThread
protected abstract fun createCall(): LiveData<ApiResponse<RequestType>>
}
以下是一个运用NetworkBoundResource形式的代码示例,假定咱们有一个数据模型User:
data class User(val id: Int, val name: String, val email: String)
咱们将运用Room作为本地数据库结构,Retrofit作为长途数据源结构,以及ViewModel和LiveData作为出现数据的组件。 首要,咱们需求创立一个包括本地缓存和长途数据源交互逻辑的仓库类:
class UserRepository(private val userDao: UserDao, private val userService: UserService) {
fun getUser(id: Int): LiveData<Resource<User>> {
return object : NetworkBoundResource<User, User>() {
override fun loadFromDb(): LiveData<User> {
return userDao.getUserById(id)
}
override fun shouldFetch(data: User?): Boolean {
return data == null
}
override fun saveCallResult(item: User) {
userDao.insertUser(item)
}
override fun createCall(): LiveData<ApiResponse<User>> {
return userService.getUser(id)
}
}.asLiveData()
}
}
在上面的示例中,咱们创立了一个名为getUser的办法,该办法回来LiveData<Resource>类型。在该办法中,咱们创立了一个NetworkBoundResource对象,并重写了它的四个办法:loadFromDb、shouldFetch、saveCallResult和createCall。
- loadFromDb:从本地缓存中加载数据。
- shouldFetch:决议是否需求从长途数据源获取数据。
- saveCallResult:将从长途数据源获取的数据存储到本地缓存中。
- createCall:创立一个Retrofit的LiveData对象,用于从长途数据源获取数据。
接下来,咱们需求定义一个ViewModel类,用于将数据出现给UI:
class UserViewModel(private val userRepository: UserRepository) : ViewModel() {
private val _user = MutableLiveData<Resource<User>>()
val user: LiveData<Resource<User>>
get() = _user
fun getUser(id: Int) {
_user.value = Resource.loading(null)
userRepository.getUser(id).observeForever { result ->
_user.value = result
}
}
}
在上面的示例中,咱们定义了一个getUser办法,该办法经过调用UserRepository的getUser办法来获取用户数据,并运用LiveData将数据出现给UI。 最后,咱们需求在UI层(如Activity或Fragment)中观察UserViewModel的user特点,以获取用户数据并更新UI:
class UserActivity : AppCompatActivity() {
private val viewModel by viewModels<UserViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_user)
viewModel.user.observe(this, { result ->
when (result.status) {
Status.SUCCESS -> {
val user = result.data
// 更新UI
}
Status.ERROR -> {
val message = result.message ?: getString(R.string.unknown_error)
// 显现错误信息
}
Status.LOADING -> {
// 显现加载中状态
}
}
})
viewModel.getUser(1)
}
}
在上面的示例中,咱们运用observe办法观察UserViewModel的user特点,并依据不同的状态更新UI。在onCreate办法中,咱们调用getUser办法来获取用户数据。