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