0

I am having a situation, where once I get pagingData <T: UIModel>, I need to get additional data from a different API. The second Api requires arguments that are there in first API response. Currently I am collecting in UI Layer in lifecyclescope as,

            loadResults().collectLatest {

                PagingResultAdapter.submitData(lifecycle, it)
               
                // Extracting the data inside PagingData and setting in viewmodel.
                it.map { uiModel -> 
                    Timber.e("Getting data inside map function..")
                    viewModel.setFinalResults(uiModel)
                }
            }
        }

But the problem is, the map{} function on pagingData won't run during data fetching. List is populated, ui is showing the items in recyclerview. But the map function not running..(I am not able see the log)

The UI layer loadResults() function in-turn calls the viewmodel.loadResults() with UI level variables. In terms of paging everything is working fine, but I cannot transform the pagingdata into UIModel in any layer. Official site suggests to use map{} function only.

https://developer.android.com/topic/libraries/architecture/paging/v3-transform#basic-transformations

But I am not getting at which layer I should apply map{} and also before collecting or after collecting..Any help is good..

Willey Hute
  • 939
  • 13
  • 18

2 Answers2

2

PagingData.map is a lazy transformation that runs during collection when you call .submitData(pagingData). Since you are only submitting the original un-transformed PagingData your .map transform will never run.

You should apply the .map to the PagingData you will actually end up submitting in order to have it run. Usually this is done from the ViewModel, so that the results are also cached in case you end up in a config change or cached scenario like when navigating between fragments.

You didn't share your ViewModel / place you are creating your Pager, but assuming this happens at a different layer you would have something like:

MyViewModel.kt

fun loadResults() = Pager(...) { ... }
  .flow
  .map {
    Timber.e("Getting data inside map function..")
    setFinalResults(uiModel)
    it
  }
  .cachedIn(viewModelScope)

MyUi.kt

viewModel.loadResults().collectLatest {
  pagingDataAdapter.submitData(it)
}

NOTE: You should use the suspending version of .submitData since you are using Flow / Coroutines, because it is able to propagate cancellation direction instead of relying on launched job + eager cancellation via the non-suspending version. There shouldn't be any visible impact, but it is more performant.

dlam
  • 3,547
  • 17
  • 20
  • Yeah..applying ```map{}``` on paging data is to be done in ViewModel only and also before I call ```cachedIn()```. But when I was trying the same in VM, with setFinalResults(uiModel), the function was only setting the ViewModel property and returning ```Kotlin.Unit```. So for the downstream, I got only ```PagingData```. – Willey Hute Mar 09 '22 at 09:42
  • So finally, I am applying ```map{}``` as you said in VM and before ```cachedIn()``` call only. But my ```setFinalResults()``` function is now returning UIModel and the downstream also getting the ```PagingData```. – Willey Hute Mar 09 '22 at 09:45
  • Ah map actually transforms the item, so you need to return the original item if all you care about it the side effect. I've edited my answer. – dlam Mar 09 '22 at 19:11
  • Yeah..now it is more convincing... – Willey Hute Mar 10 '22 at 05:18
  • cant call map block in pager – Reza Zavareh Sep 10 '22 at 07:28
  • @RezaZavareh sorry, didn't see this until now - I was missing the property access for `Flow`. The .map is Flow's .map operator. Fixed it now. – dlam Nov 02 '22 at 19:51
0

Try with:

import androidx.paging.map

.flow.map { item ->    
    item.map { it.yourTransformation() }
}
Lorenzo
  • 116
  • 1
  • 5