在Android开发里,常常需要处理网络加载数据,这里基于RxJava,和Kotlin的Sealed class(封装类)的特性实现的一个示例。
Resource.kt
首先使用Kotlin的sealed class实现Resource.kt,它用来包装数据和状态。
sealed class Resource<out T> {
class Loading<out T> : Resource<T>()
data class Success<out T>(val data: T?) : Resource<T>()
data class Failure<out T>(val throwable: Throwable) : Resource<T>()
}
其中类型参数T使用了修饰符out来标记,这是为了确保它只能由Resource的成员(Loading,Success和Failure)生成返回,而不能被消费使用。
Content.isNetworkStatusAvailable
使用Kotlin的扩展特性给Content添加isNetworkStatusAvailable扩展,它用于检测网络状态是否可用。
fun Context.isNetworkStatusAvailable(): Boolean {
val connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager
connectivityManager?.let {
it.activeNetworkInfo?.let {
if (it.isConnected) return true
}
}
return false
}
NetworkBoundResource
NetworkBoundResource网络绑定资源类,它主要用来监察数据库。根据是否有网络来处理数据:
- 网络不可用,直接从数据库获取数据分发出去。
- 网络可用时,为了避免用户长时间看到等待加载数据,它会先从数据库获取数据分发,然后等待网络加载完数据后再更新数据库,最后再从数据库获取数据分发出去。
abstract class NetworkBoundResource<ResultType, RequestType>(context: Context) {
private val result: Flowable<Resource<ResultType>>
init {
// Lazy disk observable.
val diskObservable = Flowable.defer {
loadFromDb()
// Read from disk on Computation Scheduler
.subscribeOn(Schedulers.computation())
}
// Lazy network observable.
val networkObservable = Flowable.defer {
createCall()
// Request API on IO Scheduler
.subscribeOn(Schedulers.io())
// Read/Write to disk on Computation Scheduler
.observeOn(Schedulers.computation())
.doOnNext { request: Response<RequestType> ->
if (request.isSuccessful) {
saveCallResult(processResponse(request))
}
}
.onErrorReturn { throwable: Throwable ->
when (throwable) {
is HttpException -> {
throw Exceptions.propagate(NetworkExceptions.getNoServerConnectivityError(context))
}
is IOException -> {
throw Exceptions.propagate(NetworkExceptions.getNoNetworkConnectivityError(context))
}
else -> {
throw Exceptions.propagate(NetworkExceptions.getUnexpectedError(context))
}
}
}
.flatMap { loadFromDb() }
}
result = when {
context.isNetworkStatusAvailable() -> networkObservable
.map<Resource<ResultType>> { Resource.Success(it) }
.onErrorReturn { Resource.Failure(it) }
// Read results in Android Main Thread (UI)
.observeOn(AndroidSchedulers.mainThread())
.startWith(Resource.Loading())
else -> Flowable.concat(diskObservable, networkObservable)
.map<Resource<ResultType>> { Resource.Success(it) }
.onErrorReturn { Resource.Failure(it) }
// Read results in Android Main Thread (UI)
.observeOn(AndroidSchedulers.mainThread())
.startWith(Resource.Loading())
}
}
fun asFlowable(): Flowable<Resource<ResultType>> {
return result
}
private fun processResponse(response: Response<RequestType>): RequestType {
return response.body()!!
}
protected abstract fun saveCallResult(request: RequestType)
protected abstract fun loadFromDb(): Flowable<ResultType>
protected abstract fun createCall(): Flowable<Response<RequestType>>
}
Repository
一般会使用Repository来分离获取数据,并映射数据到业务模型里,示例代码如下:
fun loadContents(): Flowable<Resource<List<Content>>> {
return object : NetworkBoundResource<List<Content>, NetworkResponse>(context) {
override fun saveCallResult(request: NetworkResponse) {
contentDao.insertContents(request.data)
}
override fun loadFromDb(): Flowable<List<Content>> {
return contentDao.getContents()
}
override fun createCall(): Flowable<Response<NetworkResponse>> {
return contentService.getContents()
}
}.asFlowable()
}
Activity
在Activity里请求和处理加载数据如下:
private fun loadContents() {
disposables.add(contentsViewModel.loadContents()
.subscribe({ t: Resource<List<Content>> ->
when (t) {
is Resource.Loading -> {
// show loading state
}
is Resource.Success -> {
// show data
}
is Resource.Failure -> {
// show error state
}
else -> throw IllegalStateException("State not known or implemented.")
}
}, { t: Throwable ->
throw OnErrorNotImplementedException(t) // Explicitly throw this exception to debug.
}, { Timber.e("Completed.") }))
}
参考:https://android.jlelse.eu/networkboundresource-with-rxjava-and-kotlin-sealed-classes-1574bc516f82