Paging3:从其他片段返回时,在适配器上调用 refresh() 不会触发刷新
Paging3: calling refresh() on adapter doesn't trigger refresh when returning from other fragment
我正在为我的应用程序使用最新的 Paging3 库,它有一个显示照片列表的画廊屏幕,以及一个显示更多选项和照片信息的详细信息屏幕。我已经设置了图库以在我的片段 onCreate
:
中获取照片列表
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// display all photos, sorted by latest
viewModel.getAllPhotos()
}
如果成功,照片会通过 submitList
传递给适配器,如果用户下拉图库屏幕,它应该会触发刷新,所以我相应地设置了 refreshListener
.我在 onViewCreated
上执行此操作(请注意,我使用 ViewBinding):
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 && !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(),
"\uD83D\uDE28 Wooops ${it.error}",
Snackbar.LENGTH_LONG).show()
}
}
swipeLayout.apply {
setOnRefreshListener {
isRefreshing = true
adapter.refresh()
}
}
}
第一次加载时,下拉布局触发刷新成功。但是,在我导航到详细信息屏幕后出现了问题。在详细信息屏幕中,按后退按钮 returns 用户返回图库。如果用户拉布局,进度指示器出现但 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) }
}
}
谁能用这个给我指出正确的方向?
编辑:正如 Jay Dangar 所说,将 viewModel.getAllPhotos()
移动到 onResume
将使对 adapter.refresh()
的调用成功触发。但是,我不想每次从详细信息屏幕导航到图库时都获取所有照片。为了避免这种情况,我在拉布局时没有调用 adapter.refresh()
,而是调用了 viewModel.getAllPhotos()
。
我仍然不明白为什么接受的答案有效,但我假设 adapter.refresh()
仅在创建新的 PagingSource
或其他内容时有效。
把你的refersh逻辑放到onResume()而不是onCreate(),这是生命周期管理的问题。
我正在为我的应用程序使用最新的 Paging3 库,它有一个显示照片列表的画廊屏幕,以及一个显示更多选项和照片信息的详细信息屏幕。我已经设置了图库以在我的片段 onCreate
:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// display all photos, sorted by latest
viewModel.getAllPhotos()
}
如果成功,照片会通过 submitList
传递给适配器,如果用户下拉图库屏幕,它应该会触发刷新,所以我相应地设置了 refreshListener
.我在 onViewCreated
上执行此操作(请注意,我使用 ViewBinding):
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 && !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(),
"\uD83D\uDE28 Wooops ${it.error}",
Snackbar.LENGTH_LONG).show()
}
}
swipeLayout.apply {
setOnRefreshListener {
isRefreshing = true
adapter.refresh()
}
}
}
第一次加载时,下拉布局触发刷新成功。但是,在我导航到详细信息屏幕后出现了问题。在详细信息屏幕中,按后退按钮 returns 用户返回图库。如果用户拉布局,进度指示器出现但 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) }
}
}
谁能用这个给我指出正确的方向?
编辑:正如 Jay Dangar 所说,将 viewModel.getAllPhotos()
移动到 onResume
将使对 adapter.refresh()
的调用成功触发。但是,我不想每次从详细信息屏幕导航到图库时都获取所有照片。为了避免这种情况,我在拉布局时没有调用 adapter.refresh()
,而是调用了 viewModel.getAllPhotos()
。
我仍然不明白为什么接受的答案有效,但我假设 adapter.refresh()
仅在创建新的 PagingSource
或其他内容时有效。
把你的refersh逻辑放到onResume()而不是onCreate(),这是生命周期管理的问题。