Paging 3 in android not working on initial load

30 Views Asked by At

I have a fragment "webpages" implemented in a search activity. The problem is that when I click to open the fragment the api request is sent through the load ftn in paging source and I think the loader saves that data,and the next time I click the button to open the fragment (in memory) *if the api has returned the response by then * the response data shows in the recycler view, I really cant understand why it takes two clicks My code

WebPagesFragment

package com.example.browserapp.fragments

import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.Button
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.asLiveData
import androidx.lifecycle.lifecycleScope
import androidx.paging.LoadState
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.example.browserapp.R
import com.example.browserapp.adapters.WebpagesSearchAdapter
import com.example.browserapp.search.searchTerm
import com.example.browserapp.viewmodels.WebPagesViewModel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch

class WebPagesFragment(private val btnBinding: Button) : Fragment(R.layout.fragment_web_pages) {
    private lateinit var rvSearchResult: RecyclerView
    private val viewModel: WebPagesViewModel by activityViewModels()
    private lateinit var adapter: WebpagesSearchAdapter
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

    }
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        viewModel.query = searchTerm
        rvSearchResult = view.findViewById(R.id.rvWebpagesSearchResult)
        rvSearchResult.layoutManager = LinearLayoutManager(context)
        rvSearchResult.setHasFixedSize(true)

        adapter =
            WebpagesSearchAdapter(WebpagesSearchAdapter.DIFF_CALLBACK) // Use DiffUtil callback
        observePagingData()
        rvSearchResult.adapter = adapter
//        adapter.refresh()
//        viewModel.fetchWebSearchResults(searchTerm) // Trigger initial loading

    }

    private fun observePagingData() {
        Log.d("TAGINN2", "observe PAGING DATA ftn")
        adapter.addLoadStateListener { loadState ->
            // Handle loading and error states
            Log.d("TAGINN2", "observe")
            if (loadState.refresh is LoadState.Loading) {
                Log.d("TAGINN2", "loading")
                // Show loading indicator
            } else {
                Log.d("TAGINN2", "not Loading")
                // Hide loading indicator
                val error = when {
                    loadState.prepend is LoadState.Error -> loadState.prepend as LoadState.Error
                    loadState.append is LoadState.Error -> loadState.append as LoadState.Error
                    loadState.refresh is LoadState.Error -> loadState.refresh as LoadState.Error
                    else -> null
                }

                error?.let {
                    Log.e("TAGINN2", "error occurred $error")
                    // Handle error
                }
            }
        }
        submitDataToAdapter()
    }

    private fun submitDataToAdapter() {
        viewLifecycleOwner.lifecycleScope.launch {

            viewModel.flow.collectLatest { pagingData ->
                Log.d("TAGINN2", "submitting data to adapter ${pagingData.toString()}")
                adapter.submitData(pagingData)
            }
        }
    }
}

WebPagesSearchAdapeter

package com.example.browserapp.adapters

import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.paging.PagingDataAdapter
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.example.browserapp.R
import com.example.browserapp.dataClasses.bingSearch.WebpagesSearch
import com.example.browserapp.listeners.webpagesListener

class WebpagesSearchAdapter(diffCallback: DiffUtil.ItemCallback<WebpagesSearch.WebPages.Value>) :
    PagingDataAdapter<WebpagesSearch.WebPages.Value, WebpagesSearchAdapter.ViewHolder>(diffCallback) {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        // Inflate the item layout dynamically
        val view =
            LayoutInflater.from(parent.context).inflate(R.layout.card_websearch, parent, false)
        return ViewHolder(view)

    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        try {
            val currentItem: WebpagesSearch.WebPages.Value? = getItem(position)
            // Bind data to the views in the item layout
            if (currentItem?.thumbnailUrl != null) {
                holder.bindImage(currentItem.thumbnailUrl)
                holder.thumbnailImage.visibility = View.VISIBLE // Show image view
            } else {
                holder.thumbnailImage.visibility = View.GONE // Hide image view
            }
            holder.name.text = currentItem?.name
            holder.displayUrl.text = currentItem?.displayUrl
            holder.datePublished.text = currentItem?.datePublished
            holder.snippet.text = currentItem?.snippet
            webpagesListener(holder.webpageLinearLayout, holder.itemView.context, currentItem?.url)
            // ... (bind other views as needed)
        } catch (e: Exception) {
            Log.e("TAGINN2", e.stackTraceToString())
        }
    }

    inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        //        name, displayUrl,datePublished,snippet
        // ... (reference other views in the item layout)
        val thumbnailImage: ImageView = view.findViewById(R.id.thumbnailImage)
        val name: TextView = view.findViewById(R.id.name)
        val displayUrl: TextView = view.findViewById(R.id.displayUrl)
        val datePublished: TextView = view.findViewById(R.id.datePublished)
        val snippet: TextView = view.findViewById(R.id.snippet)
        val webpageLinearLayout: LinearLayout = view.findViewById(R.id.webpageLinearLayout)
        fun bindImage(imageUrl: String?) {
            Glide.with(itemView.context)
                .load(imageUrl)
                .into(thumbnailImage)
        }

    }

    companion object {
        val DIFF_CALLBACK = object : DiffUtil.ItemCallback<WebpagesSearch.WebPages.Value>() {
            override fun areItemsTheSame(
                oldItem: WebpagesSearch.WebPages.Value,
                newItem: WebpagesSearch.WebPages.Value
            ): Boolean {
                // Return true if items represent the same web page
                val bool = oldItem.url == newItem.url
                Log.d("TAGINN2", "are items the same:$bool")
                return bool
            }

            override fun areContentsTheSame(
                oldItem: WebpagesSearch.WebPages.Value,
                newItem: WebpagesSearch.WebPages.Value
            ): Boolean {
                // Return true if items have the same content (name, url, etc.)
                val bool = oldItem == newItem
                Log.d("TAGINN2", "are contents the same:$bool")
                return bool
            }
        }
    }
}

WebPagesViewModel

package com.example.browserapp.viewmodels

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.cachedIn
import com.example.browserapp.dataClasses.bingSearch.WebpagesSearch
import com.example.browserapp.paging.WebpagesPagingSource
import com.example.browserapp.search.webSearchEndpoint
import com.example.browserapp.search.getSearchWebResultAsync
import com.example.browserapp.search.searchTerm
import com.example.browserapp.search.subscriptionKey
import com.example.browserapp.search.webpagesCount
import kotlinx.coroutines.launch

class WebPagesViewModel : ViewModel() {
    var query:String? = null
    private val _webSearchValues = MutableLiveData<WebpagesSearch?>()
    val webSearchValues: LiveData<WebpagesSearch?> = _webSearchValues
    private var isDataFetched = false
    val flow = Pager(
        // Configure Paging behavior
        PagingConfig(pageSize = webpagesCount, prefetchDistance = 5 ) // Example configuration
    ) {
        // Create PagingSource instance when needed
        WebpagesPagingSource(query)
    }.flow
        .cachedIn(viewModelScope)
//    fun fetchWebSearchResults(query:String) {
//
//        if (!isDataFetched){
//            viewModelScope.launch {
//                val result = getSearchWebResultAsync(query,0)
//                _webSearchValues.postValue(result) // Use postValue for main thread safety
//                if(result !== null){
//                    isDataFetched = true
//                }
//            }
//        }
//    }
}

WebpagesPagingSource

package com.example.browserapp.paging

import android.util.Log
import androidx.paging.LoadStates
import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.bumptech.glide.load.HttpException
import com.example.browserapp.dataClasses.bingSearch.WebpagesSearch
import com.example.browserapp.search.getSearchWebResultAsync
import com.example.browserapp.search.webpagesCount
import java.io.IOException

class WebpagesPagingSource(
    val query: String?
) : PagingSource<Int, WebpagesSearch.WebPages.Value>() {
    override suspend fun load(
        params: LoadParams<Int>
    ): LoadResult<Int, WebpagesSearch.WebPages.Value> {
        try {
            // Start refresh at page 1 if undefined.
            val nextPageNumber = params.key ?: 1
            Log.d("TAGINN2", "next page number $nextPageNumber")
            val response = getSearchWebResultAsync(query ?: "", nextPageNumber)
            val data = response?.webPages?.value?.filterNotNull() ?: emptyList()
            val nextKey = if ((response?.webPages?.totalEstimatedMatches != null) &&
                ((response.webPages.totalEstimatedMatches - webpagesCount) > nextPageNumber)
            ) {
                nextPageNumber + 1
            } else {
                null
            }
            Log.d("TAGINN2", "data is $data")
            return LoadResult.Page(
                data = data,
                prevKey = null, // Only paging forward.
                nextKey = nextKey
            )
        } catch (e: IOException) {
            // IOException for network failures.
            Log.e("TAGINN2", e.stackTraceToString())
            return LoadResult.Error(e)
        } catch (e: HttpException) {
            Log.e("TAGINN2", e.stackTraceToString())
            // HttpException for any non-2xx HTTP status codes.
            return LoadResult.Error(e)
        }
    }

    override fun getRefreshKey(state: PagingState<Int, WebpagesSearch.WebPages.Value>): Int? {

        // Try to find the page key of the closest page to anchorPosition from
        // either the prevKey or the nextKey; you need to handle nullability
        // here.
        //  * prevKey == null -> anchorPage is the first page.
        //  * nextKey == null -> anchorPage is the last page.
        //  * both prevKey and nextKey are null -> anchorPage is the
        //    initial page, so return null.
        return state.anchorPosition?.let { anchorPosition ->
            val anchorPage = state.closestPageToPosition(anchorPosition)
            anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1)
        }

    }
}

I tried many things and the only thing that worked was simulating the button press in the fragment which triggers the onclick listener to open the fragment after a delay which makes sure the data has been retrieved. but also sends my program for an infinite loop

0

There are 0 best solutions below