Индикатор загрузки не скрывает, если API не удалось получить данные, хотя он скрывается, если API удалось получить данные в библиотеке подкачки Android.

У меня есть удаленный сервер, с которого я хочу получить 20 элементов (задание) на вызов API и показать их в 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 ложным, и отображается сообщение об ошибке. это означает, что .doOnTerminate { initialLoadingState.postValue(Event(loading(false))) } не вызывается, если произошла ошибка. Это первая проблема. Другая проблема заключается в том, что loadInitial() и loadAfter() вызываются одновременно при первом вызове. Но я просто хочу, чтобы метод loadInitial() вызывался в начале. после прокрутки будет вызван метод loadAfter().


person Aminul Haque Aome    schedule 30.11.2019    source источник
comment
Почему бы не вызвать network_loading_indicator.visibilityGone(), когда ветвь is Failure верна?   -  person Fred    schedule 02.12.2019
comment
@Fred Brother, большое спасибо за ваши усилия, чтобы решить мой вопрос. это сработает, я знал. Но это не то, что я хотел. Причина в .doOnTerminate() я активировал статус логического значения в false, что означает, что его следует наблюдать во фрагменте, если сетевое подключение не удалось. Но этого не наблюдается :(   -  person Aminul Haque Aome    schedule 03.12.2019


Ответы (1)


Попробуйте заменить все postValue() методы LiveData на setValue() или просто .value =.

Проблема в том, что метод postValue() предназначен для обновления значения из фонового потока наблюдателям в основном потоке. В этом случае вы всегда меняете значения из самого основного потока, поэтому вам следует использовать .value =.

Надеюсь, еще не поздно.

person Martín Marengo    schedule 18.09.2020