Android paging 3: It is possible to get the itemcount from PagingData<T>?
Asked Answered
P

1

10

how am I able to get the current amount of items, that my PagingData<Product> holds? The only solution I found was to call shopListAdapter.itemCount but this always returns 0.

What I am trying to do is: Get the current amount of items that are currently displayed by my PagingAdapter, submit this value to my shopViewModel, and then use this value inside another fragment (DialogFragment). Or: Get the amount of items from my PagingData<Product> inside my viewmodel

Here is my current approach

MainFragment

@AndroidEntryPoint
class ShopFragment : Fragment(R.layout.fragment_shop), ShopAdapter.OnItemClickListener {
    private val shopViewModel: ShopViewModel by navGraphViewModels(R.id.nav_shop) { defaultViewModelProviderFactory }
    private var _shopBinding: FragmentShopBinding? = null
    private val shopBinding: FragmentShopBinding get() = _shopBinding!!
    @Inject lateinit var shopListAdapter: ShopAdapter

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        _shopBinding = FragmentShopBinding.inflate(inflater, container, false)
        return shopBinding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        collectShopList()
    }

    private fun collectShopList(){
        shopViewModel.shopPagingData.observe(viewLifecycleOwner)  {
            shopListAdapter.submitData(viewLifecycleOwner.lifecycle, it)
            shopViewModel.setItemAmount(shopListAdapter.itemCount)
        }
    }
}

DialogFragment

@AndroidEntryPoint
class ShopFilterFragment : DialogFragment() {
    private val shopViewModel: ShopViewModel by navGraphViewModels(R.id.nav_shop) { defaultViewModelProviderFactory }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_shop_filter, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        shopViewModel.itemAmount.observe(viewLifecycleOwner) {
            toast(it.toString()) <-- Always displays 0
        }
    }

}

ViewModel

class ShopViewModel @ViewModelInject constructor(
    private val shopRepository: ShopRepository
) : ViewModel() {
    private val _itemAmount = MutableLiveData<Int>()
    val itemAmount: LiveData<Int> get() = _itemAmount

    private val query = MutableLiveData(QueryHolder(null, null, null))

    val shopPagingData = query.switchMap { query -> shopRepository.search(query).cachedIn(viewModelScope) }

    fun search(newQuery: QueryHolder) {
        query.value = newQuery
    }

    fun setItemAmount(amount: Int) {
        _itemAmount.value = amount
    }
}
Pendulum answered 26/12, 2020 at 22:30 Comment(3)
"Get the current amount of items that are currently displayed by my PagingAdapter" -- that value does not seem like it will be meaningful. It's going to be a few pages worth of data, which may or may not resemble the amount of data to be paged through from wherever this data resides. If you really mean "get the total number of possible items, if the user paged through everything", whether that is knowable or not without doing all of the paging depends on the data source much more than it depends on Paging 3.Stockmon
For example, if your data is backed by Room or something, you can query the count of rows matching the same criteria, and know positively how many Paging would page through (barring data changes). But, if your data is backed by a Web service that does not return the total count or total number of pages, you're not going to have any way to know the possible total count until you page through everything and eventually run out of data.Stockmon
@Stockmon Maybe I used the wrong words, so let my try to explain it again: Let's say I am requesting three pages in total. Combined, this would result in 30 different items which the user could possibly see. My question is: It is possible to calculate the amount of items which all my pages given a current query hold (here the number 30) to show it to the user? My data is not backed up by room but by cloud-firestore.Pendulum
S
15

adapter.itemCount is the correct way, but note that there are possibilities for it to race with what the adapter has received (page will load and then notify adapter - if you call .itemCount between those steps it will return the incorrect number).

To wait for adapter to present the page, the easiest way is to use loadStateListener / loadStateFlow, as LoadState updates are guaranteed to be synchronous with adapter events.

shopListAdapter.addLoadStateListener { combinedLoadStates ->
  // If you don't want to call all the time, you
  // can filter on changes in combinedLoadStates
  shopViewModel.setItemAmount(shopListAdapter.itemCount)
}

Don't forget to unregister in case of leaks.

Alternatively, you can use flow based approach:

.. onViewCreated(..) {
  viewLifecycleOwner.lifecycleScope.launch {
    shopListAdapter.loadStateFlow.collect { combinedLoadStates ->
      // If you don't want to call all the time, you
      // can filter on changes in combinedLoadStates
      shopViewModel.setItemAmount(shopListAdapter.itemCount)
    }
  }
}
Standardize answered 28/12, 2020 at 18:49 Comment(1)
Would adapter.snapshot().items.size be more accurate? In case we have placeholders enabled. For me adapter.itemCount returns the total number of items in the DB.Necrophobia

© 2022 - 2024 — McMap. All rights reserved.