场景:经过网络恳求回来的数据来更新View

先抛出一个很常见的问题:如何经过网络恳求的数据来更新View?这个问题的解决方案是很明确的:

  1. 在本地缓存/数据库,中查找是否有缓存,假如有缓存直接更新View。
  2. 假如没有找到缓存,建议网络恳求。
  3. 网络恳求回来成果后,更新缓存,更新View。

LiveData 在 NetworkBoundResource 中的巧妙应用

NetworkBoundResource 是什么

NetworkBoundResource是一种Android Jetpack架构组件中的设计形式,完结的就是上述的数据请过过程。由于View的改写依赖于网络恳求的回来的成果,而网络恳求又需求必定的时间,整个过程是一个同步操作,代码中往往也需求传递callback,来完结View的更新。NetworkBoundResource 经过引入LiveData,把数据的恳求变成了一个异步的操作,View的更新经过LiveData 的 observe 来完结。

LiveData 在 NetworkBoundResource 中的巧妙应用
上图中能够看到,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办法来获取用户数据。