基于RxJava和Kotlin封装类处理网络加载数据

时间:2022-06-01 20:47:40

在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网络绑定资源类,它主要用来监察数据库。根据是否有网络来处理数据:

  1. 网络不可用,直接从数据库获取数据分发出去。
  2. 网络可用时,为了避免用户长时间看到等待加载数据,它会先从数据库获取数据分发,然后等待网络加载完数据后再更新数据库,最后再从数据库获取数据分发出去。
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