Страница 3: вызов refresh() на адаптере не запускает обновление при возврате из другого фрагмента

#android #rx-java2 #android-jetpack #android-paging #android-paging-3

#Android #rx-java2 #android-jetpack #android-подкачка #android-paging-3

Вопрос:

Я использую последнюю библиотеку Paging3 для своего приложения, в которой есть экран галереи, отображающий список фотографий, и экран сведений, показывающий дополнительные параметры и информацию о фотографии. Я настроил галерею для получения списка фотографий в моем фрагменте onCreate :

     override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // display all photos, sorted by latest
        viewModel.getAllPhotos()
    }
  

В случае успеха фотографии передаются адаптеру через submitList , и если пользователь опускает экран галереи, это должно вызвать обновление, поэтому я настроил refreshListener соответствующим образом. Я делаю это на onViewCreated (обратите внимание, что я использую привязку к просмотру):

     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        binding = FragmentGalleryBinding.bind(view)

        viewLifecycleOwner.lifecycle.addObserver(viewModel)

        setupGallery()

        setupRetryButton()
    }

    private fun setupGallery() {
        // Add a click listener for each list item
        adapter = GalleryAdapter{ photo ->
            photo.id.let {
                findNavController().navigate(GalleryFragmentDirections.detailsAction(it))
            }
        }

        viewModel.uiState?.observe(viewLifecycleOwner, {
            binding?.swipeLayout?.isRefreshing = false
            adapter.submitData(lifecycle, it)
        })

        binding?.apply {
            // Apply the following settings to our recyclerview
            list.adapter = adapter.withLoadStateHeaderAndFooter(
                header = RetryAdapter {
                    adapter.retry()
                },
                footer = RetryAdapter {
                    adapter.retry()
                }
            )

            // Add a listener for the current state of paging
            adapter.addLoadStateListener { loadState ->
                Log.d("GalleryFragment", "LoadState: "   loadState.source.refresh.toString())
                // Only show the list if refresh succeeds.
                list.isVisible = loadState.source.refresh is LoadState.NotLoading
                // do not show SwipeRefreshLayout's progress indicator if LoadState is NotLoading
                swipeLayout.isRefreshing = loadState.source.refresh !is LoadState.NotLoading
                // Show loading spinner during initial load or refresh.
                progressBar.isVisible = loadState.source.refresh is LoadState.Loading amp;amp; !swipeLayout.isRefreshing
                // Show the retry state if initial load or refresh fails.
                retryButton.isVisible = loadState.source.refresh is LoadState.Error

                val errorState = loadState.source.append as? LoadState.Error
                    ?: loadState.source.prepend as? LoadState.Error
                    ?: loadState.append as? LoadState.Error
                    ?: loadState.prepend as? LoadState.Error
                errorState?.let {
                    swipeLayout.isRefreshing = false
                    Snackbar.make(requireView(),
                        "uD83DuDE28 Wooops ${it.error}",
                        Snackbar.LENGTH_LONG).show()
                }
            }

            swipeLayout.apply {
                setOnRefreshListener {
                    isRefreshing = true
                    adapter.refresh()
                }
            }
        }
  

При первой загрузке удаление макета успешно запускает обновление. Однако проблема возникает после перехода к экрану сведений. На экране сведений нажатие кнопки «Назад» возвращает пользователя в галерею. Если пользователи извлекают макет, появляется индикатор выполнения, но adapter.refresh() этого не происходит. Я в недоумении относительно того, как это отладить.

Для справки, вот как ViewModel выглядит мой, который отвечает за выборку фотографий:

 class GalleryViewModel(private val getAllPhotosUseCase: GetAllPhotosUseCase): BaseViewModel() {

    private val _uiState = MutableLiveData<PagingData<UnsplashPhoto>>()
    val uiState: LiveData<PagingData<UnsplashPhoto>>? get() = _uiState

    fun getAllPhotos() {
        compositeDisposable  = getAllPhotosUseCase.getAllPhotos()
            .cachedIn(viewModelScope)
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribeBy(
                onNext = { _uiState.value = it },
                onError = {
                    it.printStackTrace()
                }
            )
    }

}
  

GetAllPhotosUseCase Перенаправляет getAllPhotos вызов на Repository реализацию, содержащую следующее:

 class UnsplashRepoImpl(private val unsplashApi: UnsplashApi): UnsplashRepo {

    override fun getAllPhotos(): Observable<PagingData<UnsplashPhoto>> = Pager(
        config = PagingConfig(Const.PAGE_SIZE),
        remoteMediator = null,
        // Always create a new UnsplashPagingSource object. Failure to do so would result in a
        // IllegalStateException when adapter.refresh() is called--
        // Exception message states that the same PagingSource was used as the prev request,
        // and a new PagingSource is required
        pagingSourceFactory = { UnsplashPagingSource(unsplashApi) }
    ).observable

....
}
  

Моя RxPagingSource настройка выглядит следующим образом:

 class UnsplashPagingSource (private val unsplashApi: UnsplashApi)
    : RxPagingSource<Int, UnsplashPhoto>(){

    override fun loadSingle(params: LoadParams<Int>): Single<LoadResult<Int, UnsplashPhoto>> {
        val id = params.key ?: Const.PAGE_NUM
        return unsplashApi.getAllPhotos(id, Const.PAGE_SIZE, "latest")
        .subscribeOn(Schedulers.io())
            .map { response ->
                response.map { it.toUnsplashPhoto() }
            }
            .map<LoadResult<Int, UnsplashPhoto>> { item ->
                LoadResult.Page(
                    data = item,
                    prevKey = if (id == Const.PAGE_NUM) null else id - 1,
                    nextKey =  if (item.isEmpty()) null else id   1
                )
            }
            .onErrorReturn { e -> LoadResult.Error(e) }
    }
}
  

Может ли кто-нибудь указать мне правильное направление с этим?

РЕДАКТИРОВАТЬ: как сказал Джей Дангар, переход viewModel.getAllPhotos() к onResume вызову adapter.refresh() приведет к успешному запуску вызова. Однако я не хочу извлекать все фотографии каждый раз, когда я перехожу с экрана сведений в галерею. Чтобы избежать этого, вместо вызова adapter.refresh() при извлечении макета я просто вызываю viewModel.getAllPhotos() вместо этого.

Я все еще не понимаю, почему принятый ответ работает, но я предполагаю, что adapter.refresh() это работает только при создании нового PagingSource или чего-то в этом роде.

Комментарии:

1. Похоже, это может быть ошибка в библиотеке. Я предполагаю, что submitData определяет область для канала обновления внутри, и переход от фрагмента отменяет область. Сохраняется ли проблема, если вы используете жизненный цикл действия?

2. @dlam Я пробовал использовать adapter.submitData(requireActivity().lifecycle, it) with adapter.refresh() , и обновление по-прежнему не происходит.

Ответ №1:

поместите свою логику ссылок в onResume() вместо onCreate(), это проблема управления жизненным циклом.

Комментарии:

1. Код в onCreate не является частью логики обновления, однако при его перемещении onResume последующие вызовы будут adapter.refresh() работать после перехода с экрана сведений.