Я реализую подкачку из сети + кеш базы данных, используя новую библиотеку Paging 3 . Моя реализация похожа на пример Android Paging codelab . Наиболее существенное различие заключается в том, что я хочу отображать данные БД сразу во время загрузки сетевых данных. Я реализовал RemoteMediator
и использую PagingSource
, предоставленный базой данных Room.
@Dao
interface FooDao {
...
@Query("SELECT * FROM foo")
fun getFooSource(): PagingSource<Int, Foo>
}
По умолчанию данные не отображаются в RecycleView
, если нет сетевого подключения. Чтобы сразу отображать данные БД из PagingSource
, мой RemoteMediator
использует InitializeAction.SKIP_INITIAL_REFRESH
, и это решает проблему.
class MyRemoteMediator(...) : RemoteMediator<Int, Foo>() {
...
override suspend fun initialize(): InitializeAction {
return InitializeAction.SKIP_INITIAL_REFRESH
}
}
Теперь данные БД отображаются сразу, но сетевые данные не загружаются, поэтому я использую PagingDataAdapter.refresh()
метод для запуска запроса. Я не уверен, где правильное место для вызова refresh()
, это не сработает, если я позвоню, например, внутри Fragment.onCreate()
, поэтому давайте отложим вызов. Я не думаю, что это правильный путь, просто для примера:
class MyFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycleScope.launch {
delay(1000)
adapter.refresh()
}
}
}
Теперь он работает почти так, как ожидалось, данные БД отображаются сразу, а RemoteMediator
загружает данные из сети и обновляет базу данных. Единственная проблема, если нет inte rnet access RemoteMediator
запрос не выполняется и возвращает MediatorResult.Error
. Если я повторю запрос позже, используя метод PagingDataAdapter.retry()
, при прокрутке списка я получаю следующую ошибку:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.icesmith.tvprogramepg, PID: 6381
java.lang.IllegalStateException: Cannot coerce hint when no pages have loaded
at androidx.paging.PagerState.withCoercedHint$paging_common(PagerState.kt:313)
at androidx.paging.PageFetcherSnapshot.doLoad(PageFetcherSnapshot.kt:369)
at androidx.paging.PageFetcherSnapshot$startConsumingHints$2$invokeSuspend$$inlined$collect$1.emit(Collect.kt:137)
at kotlinx.coroutines.flow.FlowKt__ChannelsKt.emitAllImpl$FlowKt__ChannelsKt(Channels.kt:59)
at kotlinx.coroutines.flow.FlowKt__ChannelsKt$emitAllImpl$1.invokeSuspend(Unknown Source:11)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
at kotlinx.coroutines.EventLoop.processUnconfinedEvent(EventLoop.common.kt:69)
at kotlinx.coroutines.DispatchedTaskKt.resumeUnconfined(DispatchedTask.kt:184)
at kotlinx.coroutines.DispatchedTaskKt.dispatch(DispatchedTask.kt:108)
at kotlinx.coroutines.CancellableContinuationImpl.dispatchResume(CancellableContinuationImpl.kt:306)
at kotlinx.coroutines.CancellableContinuationImpl.completeResume(CancellableContinuationImpl.kt:393)
at kotlinx.coroutines.channels.AbstractChannel$ReceiveElement.completeResumeReceive(AbstractChannel.kt:888)
at kotlinx.coroutines.channels.ConflatedChannel.offerInternal(ConflatedChannel.kt:62)
at kotlinx.coroutines.channels.ConflatedBroadcastChannel$Subscriber.offerInternal(ConflatedBroadcastChannel.kt:295)
at kotlinx.coroutines.channels.ConflatedBroadcastChannel.offerInternal(ConflatedBroadcastChannel.kt:257)
at kotlinx.coroutines.channels.ConflatedBroadcastChannel.offer(ConflatedBroadcastChannel.kt:238)
at androidx.paging.PageFetcherSnapshot.addHint(PageFetcherSnapshot.kt:179)
at androidx.paging.PageFetcher$PagerUiReceiver.addHint(PageFetcher.kt:108)
at androidx.paging.PagingDataDiffer.get(PagingDataDiffer.kt:113)
at androidx.paging.AsyncPagingDataDiffer.getItem(AsyncPagingDataDiffer.kt:241)
at androidx.paging.PagingDataAdapter.getItem(PagingDataAdapter.kt:150)
at com.icesmith.tvprogramepg.fragment.channellist.ChannelAdapter.onBindViewHolder(ChannelAdapter.kt:31)
at com.icesmith.tvprogramepg.fragment.channellist.ChannelAdapter.onBindViewHolder(ChannelAdapter.kt:13)
at androidx.recyclerview.widget.RecyclerView$Adapter.onBindViewHolder(RecyclerView.java:7163)
at androidx.recyclerview.widget.RecyclerView$Adapter.bindViewHolder(RecyclerView.java:7243)
at androidx.recyclerview.widget.RecyclerView$Recycler.tryBindViewHolderByDeadline(RecyclerView.java:6110)
at androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:6376)
at androidx.recyclerview.widget.GapWorker.prefetchPositionWithDeadline(GapWorker.java:288)
at androidx.recyclerview.widget.GapWorker.flushTaskWithDeadline(GapWorker.java:345)
at androidx.recyclerview.widget.GapWorker.flushTasksWithDeadline(GapWorker.java:361)
at androidx.recyclerview.widget.GapWorker.prefetch(GapWorker.java:368)
at androidx.recyclerview.widget.GapWorker.run(GapWorker.java:399)
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7356)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)