retrofit2 - 如果 api 检索数据失败,加载指示器不会隐藏,尽管它会在 api 成功检索 Android Paging 库中的数据时隐藏

标签 retrofit2 rx-java2 android-livedata android-paging

我有一个远程服务器,我想从那里获取每个 api 调用的 20 个项目(作业),并使用分页库在 RecyclerView 中显示它们。

为此,当从服务器获取项目列表时,我想在第一个 api 调用的开头显示一个加载指示器。如果数据获取成功,一切正常。这意味着如果数据加载成功,加载指示器将不可见。代码如下。
JobService.KT

@GET(Constants.API_JOB_LIST)
fun getJobPost(
    @Query("page") pageNumber: Int
): Observable<Response<JobResponse>>
JobResponse.kt
data class JobResponse(
    @SerializedName("status") val status: Int? = null,
    @SerializedName("message") val message: Any? = null,
    @SerializedName("data") val jobData: JobData? = null
)
JobData.kt
data class JobData(
    @SerializedName("jobs") val jobs: List<Job?>? = null,
    @SerializedName("total") val totalJob: Int? = null,
    @SerializedName("page") val currentPage: Int? = null,
    @SerializedName("showing") val currentlyShowing: Int? = null,
    @SerializedName("has_more") val hasMore: Boolean? = null
)
NetworkState.kt
sealed class NetworkState {
    data class Progress(val isLoading: Boolean) : NetworkState()
    data class Failure(val errorMessage: String?) : NetworkState()

    companion object {
        fun loading(isLoading: Boolean): NetworkState = Progress(isLoading)
        fun failure(errorMessage: String?): NetworkState = Failure(errorMessage)
    }
}
Event.kt
open class Event<out T>(private val content: T) {

    private var hasBeenHandled = false

    fun getContentIfNotHandled() = if (hasBeenHandled) {
        null
    } else {
        hasBeenHandled = true
        content
    }

    fun peekContent() = content
}
JobDataSource.kt
class JobDataSource(
    private val jobService: JobService,
    private val compositeDisposable: CompositeDisposable
) : PageKeyedDataSource<Int, Job>() {

    val paginationState: MutableLiveData<Event<NetworkState>> = MutableLiveData()
    val initialLoadingState: MutableLiveData<Event<NetworkState>> = MutableLiveData()
    val totalJob: MutableLiveData<Event<Int>> = MutableLiveData()

    companion object {
        private const val FIRST_PAGE = 1
    }

    override fun loadInitial(params: LoadInitialParams<Int>, callback: LoadInitialCallback<Int, Job>) {
        compositeDisposable += jobService.getJobPost(FIRST_PAGE)
            .performOnBackgroundOutputOnMain()
            .doOnSubscribe { initialLoadingState.postValue(Event(loading(true))) }
            .doOnTerminate { initialLoadingState.postValue(Event(loading(false))) }
            .subscribe({
                if (it.isSuccessful) {
                    val jobData = it.body()?.jobData

                    totalJob.postValue(Event(jobData?.totalJob!!))
                    jobData.jobs?.let { jobs -> callback.onResult(jobs, null, FIRST_PAGE+1) }

                } else {
                    val error = Gson().fromJson(it.errorBody()?.charStream(), ApiError::class.java)

                    when (it.code()) {
                        CUSTOM_STATUS_CODE -> initialLoadingState.postValue(Event(failure(error.message!!)))
                        else -> initialLoadingState.postValue(Event(failure("Something went wrong")))
                    }
                }
            }, {
                if (it is IOException) {
                    initialLoadingState.postValue(Event(failure("Check Internet Connectivity")))
                } else {
                    initialLoadingState.postValue(Event(failure("Json Parsing error")))
                }
            })

    }

    override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<Int, Job>) {
        compositeDisposable += jobService.getJobPost(params.key)
            .performOnBackgroundOutputOnMain()
            .doOnSubscribe { if (params.key != 2) paginationState.postValue(Event(loading(true))) }
            .doOnTerminate { paginationState.postValue(Event(loading(false))) }
            .subscribe({
                if (it.isSuccessful) {
                    val jobData = it.body()?.jobData

                    totalJob.postValue(Event(jobData?.totalJob!!))
                    jobData.jobs?.let { jobs -> callback.onResult(jobs, if (jobData.hasMore!!) params.key+1 else null) }

                } else {
                    val error = Gson().fromJson(it.errorBody()?.charStream(), ApiError::class.java)

                    when (it.code()) {
                        CUSTOM_STATUS_CODE -> initialLoadingState.postValue(Event(failure(error.message!!)))
                        else -> initialLoadingState.postValue(Event(failure("Something went wrong")))
                    }
                }
            }, {
                if (it is IOException) {
                    paginationState.postValue(Event(failure("Check Internet Connectivity")))
                } else {
                    paginationState.postValue(Event(failure("Json Parsing error")))
                }
            })
    }

    override fun loadBefore(params: LoadParams<Int>, callback: LoadCallback<Int, Job>) {}

}
JobDataSourceFactory.kt
class JobDataSourceFactory(
    private val jobService: JobService,
    private val compositeDisposable: CompositeDisposable
): DataSource.Factory<Int, Job>() {

    val jobDataSourceLiveData = MutableLiveData<JobDataSource>()

    override fun create(): DataSource<Int, Job> {
        val jobDataSource = JobDataSource(jobService, compositeDisposable)
        jobDataSourceLiveData.postValue(jobDataSource)
        return jobDataSource
    }

}
JobBoardViewModel.kt
class JobBoardViewModel(
    private val jobService: JobService
) : BaseViewModel() {

    companion object {
        private const val PAGE_SIZE = 20
        private const val PREFETCH_DISTANCE = 20
    }

    private val jobDataSourceFactory: JobDataSourceFactory = JobDataSourceFactory(jobService, compositeDisposable)
    var jobList: LiveData<PagedList<Job>>

    init {
        val config = PagedList.Config.Builder()
            .setPageSize(PAGE_SIZE)
            .setInitialLoadSizeHint(PAGE_SIZE)
            .setPrefetchDistance(PREFETCH_DISTANCE)
            .setEnablePlaceholders(false)
            .build()
        jobList = LivePagedListBuilder(jobDataSourceFactory, config).build()
    }

    fun getPaginationState(): LiveData<Event<NetworkState>> = Transformations.switchMap<JobDataSource, Event<NetworkState>>(
        jobDataSourceFactory.jobDataSourceLiveData,
        JobDataSource::paginationState
    )

    fun getInitialLoadingState(): LiveData<Event<NetworkState>> = Transformations.switchMap<JobDataSource, Event<NetworkState>>(
        jobDataSourceFactory.jobDataSourceLiveData,
        JobDataSource::initialLoadingState
    )

    fun getTotalJob(): LiveData<Event<Int>> = Transformations.switchMap<JobDataSource, Event<Int>>(
        jobDataSourceFactory.jobDataSourceLiveData,
        JobDataSource::totalJob
    )
}
JobBoardFragment.kt
class JobBoardFragment : BaseFragment() {

    private val viewModel: JobBoardViewModel by lazy {
        getViewModel { JobBoardViewModel(ApiFactory.jobListApi) }
    }

    private val jobAdapter by lazy {
        JobAdapter {
            val bundle = Bundle()
            bundle.putInt(CLICKED_JOB_ID, it.jobId!!)
            navigateTo(R.id.jobBoard_to_jobView, R.id.home_navigation_fragment, bundle)
        }
    }

    override fun getLayoutResId() = R.layout.fragment_job_board

    override fun initWidget() {
        job_list_recycler_view.adapter = jobAdapter
        back_to_main_image_view.setOnClickListener { onBackPressed() }
    }

    override fun observeLiveData() {
        with(viewModel) {
            jobList.observe(this@JobBoardFragment, Observer {
                jobAdapter.submitList(it)
            })

            getInitialLoadingState().observe(this@JobBoardFragment, Observer {
                it.getContentIfNotHandled()?.let { state ->
                    when (state) {
                        is Progress -> {
                            if (state == loading(true)) {
                                network_loading_indicator.visible()
                            } else {
                                network_loading_indicator.visibilityGone()
                            }
                        }
                        is Failure -> context?.showToast(state.errorMessage.toString())
                    }
                }
            })

            getPaginationState().observe(this@JobBoardFragment, Observer {
                it.getContentIfNotHandled()?.let { state ->
                    when (state) {
                        is Progress -> {
                            if (state == loading(true)) {
                                pagination_loading_indicator.visible()
                            } else {
                                pagination_loading_indicator.visibilityGone()
                            }
                        }
                        is Failure -> context?.showToast(state.errorMessage.toString())
                    }
                }
            })

            getTotalJob().observe(this@JobBoardFragment, Observer {
                it.getContentIfNotHandled()?.let { state ->
                    job_board_text_view.visible()
                    with(profile_completed_image_view) {
                        visible()
                        text = state.toString()
                    }
                }
            })
        }
    }

}

但问题是,如果由于互联网连接或任何其他与服务器相关的问题导致数据获取失败,加载指示器不可见,这意味着它仍在加载,尽管我将 loadingStatus 设为 false 并显示错误消息。这意味着 .doOnTerminate { initialLoadingState.postValue(Event(loading(false))) } 如果发生错误则不会被调用。这是第一个问题。另一个问题是 loadInitial() 和 loadAfter() 在第一次调用时被同时调用。但我只想在开始时调用 loadInitial() 方法。滚动 loadAfter() 方法后将被调用。

最佳答案

尝试将 LiveData 的所有 postValue() 方法替换为 setValue() 或简单的 .value =
问题在于 postValue() 方法用于将值从后台线程更新到主线程中的观察者。在这种情况下,您总是在主线程本身更改值,因此您应该使用 .value =
希望现在还不算太晚。

关于retrofit2 - 如果 api 检索数据失败,加载指示器不会隐藏,尽管它会在 api 成功检索 Android Paging 库中的数据时隐藏,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59113748/

相关文章:

kotlin - 如何使用 Observable.zip 保存类型?

android - RxJava : retrieve data from database and return a list

android - 每次其他 LiveData 之一更新其值时,都会更新 LiveData 的值

android - SSL 握手异常 : Connection closed by peer on android app

java - Retrofit2 MVP 安卓

android - 如何使用 Retrofit 和 Gson 获取外键字段?

android - RxJava2 : Alternative to Observable<Void>

android - 使用 Room/LiveData 获取并观察三个对象

android - 单元测试我无法将 Observer 添加到 LiveData NullPointerException

android - 如何创建 POJO 类以使用动态键名进行改造