Challenge: Paging (with Paging 3) I need help

I checked the previous solutions for paging in this chapter. either it was deleted (deleted link) or i didn’t understand anything in it… in addition that it was made with paging 2 which is deprecated now…

I spent days learning about paging 3, and I created the needed files/codes, but i’m getting an empty recycler view only… nothing loads in it…

I posted the issue on Stackoverflow… no one mentioned any solution.
I posted the issue on fiverr the programmers after reading the code didn’t want to work on it. for any reason they had (after investigating the code), one of them said that the code is good but have to debug it and know what is the problem.
finally one programmer agreed to do it… we spent hours together (he used remote control to do the work on my pc, so that i can learn what is the issue). but he couldn’t find the problem.

after all he suggested that he do a separate code for me that applies paging 3 and take the data from API directly not from the repository in photogallery… but that is not the solution i need.

please check the code and tell me if there is something wrong in it or any possible fixes…

I added the page parameter to the link in the PhotoInterceptor.kt so that i can link it with the PhotoPaging.ktclass, and added a variable for page number to call next/previous page as follows.

package com.bignerdranch.android.photogallery.api

import android.app.Application
import okhttp3.HttpUrl
import okhttp3.Interceptor
import okhttp3.Request
import okhttp3.Response

private const val API_KEY = "a4c9*****ac78"

class PhotoInterceptor : Interceptor { //page. 540
    
    var page = 1
    
    override fun intercept(chain: Interceptor.Chain): Response {
        val originalRequest: Request = chain.request()

        System.out.println("here is the page : " + page)
        val newUrl: HttpUrl = originalRequest.url().newBuilder()
            .addQueryParameter("api_key", API_KEY)
            .addQueryParameter("format", "json")
            .addQueryParameter("nojsoncallback", "1")
            .addQueryParameter("extras", "url_s")
            .addQueryParameter("safesearch", "1")
            /**
             * added tha page to the link Query to use it in paging.
             */
            .addQueryParameter("page", "$page")
            .build()

        val newRequest: Request = originalRequest.newBuilder()
            .url(newUrl)
            .build()

        return chain.proceed(newRequest)
    }

}

then i created PhotoPaging.kt that had the loader

package com.bignerdranch.android.photogallery

import android.app.Application
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations
import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.bignerdranch.android.photogallery.api.*
import kotlin.Exception

private const val TAG = "PhotoPaging"

class PhotoPaging(private val app : Application) : PagingSource<Int, GalleryItem>() {
    
    override fun getRefreshKey(state: PagingState<Int, GalleryItem>): Int? {
        // Try to find the page key of the closest page to anchorPosition, from
        // either the prevKey or the nextKey, but you need to handle nullability
        // here:
        //  * prevKey == null -> anchorPage is the first page.
        //  * nextKey == null -> anchorPage is the last page.
        //  * both prevKey and nextKey null -> anchorPage is the initial page, so
        //    just return null.

        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, GalleryItem> {
        return try {
            //val page= params.key?: 1
            val page = PhotoInterceptor().page
            val response =PhotoGalleryViewModel(app).galleryItemLiveData.value
            Log.d(TAG, "$response")

            LoadResult.Page(
                data = response!!,
                prevKey = if (page == 1) null else page.minus(1),
                nextKey = if (page == 5) null else page.plus(1),

            )
        }catch (e: Exception){
            LoadResult.Error(e)
        }
    }
}

inside the PhotoGalleryViewModel.kt i added the flow to link the recyclerviewer with the PhotoPaging.kt

 package com.bignerdranch.android.photogallery

import android.app.Application
import android.content.Context
import androidx.lifecycle.*
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.cachedIn
import retrofit2.http.Query

class PhotoGalleryViewModel(private val app : Application) : AndroidViewModel(app){

    //page 550
    val galleryItemLiveData : LiveData<List<GalleryItem>>

    private val flickrFetchr = FlickrFetchr() //page 546
    private val mutableSearchTerm = MutableLiveData<String>()

    val searchTerm : String //page 552
    get() = mutableSearchTerm.value ?: ""

    init {
        mutableSearchTerm.value=QueryPreferences.getStoredQuery(app)
        galleryItemLiveData = /*FlickrFetchr().searchPhotos("Birds")*/
                            Transformations.switchMap(mutableSearchTerm){ searchTerm ->
                                if (searchTerm.isBlank()) flickrFetchr.fetchPhotos() else flickrFetchr.searchPhotos(searchTerm) //page.551
                            }
    }
    fun fetchPhotos(query: String = ""){
        QueryPreferences.setStoredQuery(app, query)
        mutableSearchTerm.value = query
    }
    /**
     * Set up a stream of PagingData
     */
    val flow = Pager(
        // Configure how data is loaded by passing additional properties to
        // PagingConfig, such as prefetchDistance.
        PagingConfig(pageSize = 5)
    ) {
        PhotoPaging(app)
    }.flow
        .cachedIn(viewModelScope)
}

after that in the PhotoGalleryFragment.kt I changed the PhotoAdapter and added the launcher of the paging3

 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        photoGalleryViewModel.galleryItemLiveData.observe(
            viewLifecycleOwner,
            Observer { galleryItems ->
                Log.d(TAG, "Have gallery items from view model $galleryItems")
               // photoRecyclerView.adapter = PhotoAdapter(galleryItems)


            })

        photoRecyclerView.adapter = PhotoAdapter(PhotoComparator)

        val pagingAdapter = PhotoAdapter(PhotoComparator)
        lifecycleScope.launch {
            photoGalleryViewModel.flow.collectLatest { pagingData ->
                pagingAdapter.submitData(pagingData)
            }
        }

        photoRecyclerView.viewTreeObserver.apply {
            if (isAlive) {
                addOnGlobalLayoutListener {
                    GridLayoutManager.LayoutParams.MATCH_PARENT
                }
            }
        }
    }

PhotoAdapter and PhotoComparator

private inner class PhotoAdapter(diffCallback: DiffUtil.ItemCallback<GalleryItem>) :
        PagingDataAdapter<GalleryItem, PhotoHolder>(diffCallback){
        override fun onBindViewHolder(holder: PhotoHolder, position: Int) {

//            val galleryItem = galleryItems[position]
//            holder.bindGalleryItem(galleryItem)

            val item: GalleryItem? = getItem(position)
            holder.bindGalleryItem(item!!)
            val placeholder: Drawable = ContextCompat.getDrawable(
                requireContext(),
                R.drawable.hala_atamleh
            ) ?: ColorDrawable()
            holder.bindDrawable(placeholder)
                thumbnailDownloader.queueThumbnail(holder, item.url)

        }

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PhotoHolder {
            val view = layoutInflater.inflate(
                R.layout.list_item_gallery,
                parent,
                false
            ) as ImageView
            return PhotoHolder(view)
        }

    }

    object PhotoComparator : DiffUtil.ItemCallback<GalleryItem>() {

        override fun areItemsTheSame(oldItem: GalleryItem, newItem: GalleryItem): Boolean {
            // Id is unique.
            return oldItem.id == newItem.id
        }

        override fun areContentsTheSame(oldItem: GalleryItem, newItem: GalleryItem): Boolean {
            return oldItem == newItem
        }
    }

I have no errors… yet i only get an empty view

PLEASE HELP!

well, I think the structure of this project wont help having paging 3?
unless we make an overall change in it, which is beyond my knowledge…
if anyone from BNR can have a look

I rebuilt the app from scratch and used some other components in it, didn’t continue till the end of the details because that would be copying the same old code.
but here is the app with

  • Paging 3 + Loadlistener and LoadState & Retry
  • jetpack Navigation
  • retrofit 2
  • webview