🤔 How to support pagination with this?
I am trying to fetch the videos but with pagination. And I already setup required things, but not sure how to fit it in the end. As I find this code a bit complex architecture to understand. I don't have much experience with flows.
What I did for paging is:
Now problem here is, in the code there are many things which are shared in a flow, which extends ViewResult, I am referring to the VideoPagerViewModel, according to the code I am able to use Flow<List> but according to my implementation I will be getting Flow<PagingData> this is where the issue is happening, I can't make the PagingData extend ViewResult directly also. So how can I achieve the pagination in this? Because in ShortsFragment I can see that code fetches all the videos at once.
ShortsFragment (🗝️ Already Present)
.
.
val states = viewModel.states
.onEach { state ->
// Await the list submission so that the adapter list is in sync with state.videoData
adapter.awaitList(state.videoData)
}
.
.
VideoPagerViewModel (🗝️ Already Present)
internal class VideoPagerViewModel(
private val repository: VideoDataRepository,
private val appPlayerFactory: AppPlayer.Factory,
private val handle: PlayerSavedStateHandle,
initialState: ViewState,
) : MviViewModel<ViewEvent, ViewResult, ViewState, ViewEffect>(initialState) {
override fun onStart() {
processEvent(LoadVideoDataEvent)
}
override fun Flow<ViewEvent>.toResults(): Flow<ViewResult> {
// MVI boilerplate
return merge(
filterIsInstance<LoadVideoDataEvent>().toLoadVideoDataResults(),
filterIsInstance<PlayerLifecycleEvent>().toPlayerLifecycleResults(),
filterIsInstance<TappedPlayerEvent>().toTappedPlayerResults(),
filterIsInstance<OnPageSettledEvent>().toPageSettledResults(),
filterIsInstance<PauseVideoEvent>().toPauseVideoResults()
)
}
private fun Flow<LoadVideoDataEvent>.toLoadVideoDataResults(): Flow<ViewResult> {
return flatMapLatest { repository.videoData() }
.map { videoData ->
val appPlayer = states.value.appPlayer
// If the player exists, it should be updated with the latest video data that came in
appPlayer?.setUpWith(videoData, handle.get())
// Capture any updated index so UI page state can stay in sync. For example, a video
// may have been added to the page before the currently active one. That means the
// the current video/page index will have changed
val index = appPlayer?.currentPlayerState?.currentMediaItemIndex ?: 0
LoadVideoDataResult(videoData, index)
}
}
.
.
.
}
VideoDataSource (🆕 Added)
class VideoDataSource(private val videoDao: VideosDao?) : PagingSource<Int, VideoData>() {
override fun getRefreshKey(state: PagingState<Int, VideoData>): Int? {
return state.anchorPosition?.let { anchorPosition ->
val anchorPage = state.closestPageToPosition(anchorPosition)
anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1)
}
}
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, VideoData> {
val page = params.key ?: 0
return try {
val videos = videoDao?.getPaginatedShorts(params.loadSize, page * params.loadSize)
?: emptyList()
LoadResult.Page(
data = videos,
prevKey = if (page == 0) null else page - 1,
nextKey = if (videos.isEmpty()) null else page + 1
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}
}
PagerPagingAdapter (🆕 Added)
internal class PagerPagingAdapter(private val imageLoader: ImageLoader) :
PagingDataAdapter<VideoData, PageViewHolder>(VideoDataDiffCallback) {
private var recyclerView: RecyclerView? = null
// Extra buffer capacity so that emissions can be sent outside a coroutine
private val clicks = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
fun clicks() = clicks.asSharedFlow()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PageViewHolder {
return LayoutInflater.from(parent.context)
.let { inflater -> ShortsPageItemBinding.inflate(inflater, parent, false) }
.let { binding ->
PageViewHolder(binding, imageLoader) { clicks.tryEmit(Unit) }
}
}
override fun onBindViewHolder(holder: PageViewHolder, position: Int) {
getItem(position)?.let(holder::bind)
}
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
this.recyclerView = recyclerView
}
override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
this.recyclerView = null
}
/**
* Attach [appPlayerView] to the ViewHolder at [position]. The player won't actually be visible in
* the UI until [showPlayerFor] is also called.
*/
suspend fun attachPlayerView(appPlayerView: AppPlayerView, position: Int) {
awaitViewHolder(position).attach(appPlayerView)
}
// Hides the video preview image when the player is ready to be shown.
suspend fun showPlayerFor(position: Int) {
awaitViewHolder(position).hidePreviewImage()
}
suspend fun renderEffect(position: Int, effect: PageEffect) {
awaitViewHolder(position).renderEffect(effect)
}
/**
* The ViewHolder at [position] isn't always immediately available. In those cases, wait for
* the RecyclerView to be laid out and re-query that ViewHolder.
*/
private suspend fun awaitViewHolder(position: Int): PageViewHolder {
if (itemCount == 0) error("Tried to get ViewHolder at position $position, but the list was empty")
var viewHolder: PageViewHolder?
do {
viewHolder = recyclerView?.findViewHolderForAdapterPosition(position) as? PageViewHolder
} while (currentCoroutineContext().isActive && viewHolder == null && recyclerView?.awaitNextLayout() == Unit)
return requireNotNull(viewHolder)
}
private object VideoDataDiffCallback : DiffUtil.ItemCallback<VideoData>() {
override fun areItemsTheSame(oldItem: VideoData, newItem: VideoData): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: VideoData, newItem: VideoData): Boolean {
return oldItem == newItem
}
}
}
🤔 How to support pagination with this?
I am trying to fetch the videos but with pagination. And I already setup required things, but not sure how to fit it in the end. As I find this code a bit complex architecture to understand. I don't have much experience with flows.
What I did for paging is:
Now problem here is, in the code there are many things which are shared in a flow, which extends ViewResult, I am referring to the
VideoPagerViewModel, according to the code I am able to use Flow<List> but according to my implementation I will be getting Flow<PagingData> this is where the issue is happening, I can't make the PagingData extend ViewResult directly also. So how can I achieve the pagination in this? Because in ShortsFragment I can see that code fetches all the videos at once.ShortsFragment (🗝️ Already Present)
VideoPagerViewModel (🗝️ Already Present)
VideoDataSource (🆕 Added)
PagerPagingAdapter (🆕 Added)