I'm currently trying to implement a list using paging 3. My current implementation allows me to load a list of data, choose a filter option but the screen is not automatically refreshing at the moment with the filtered list. It's only doing so at the start of a new screen. How could I force the screen to update automatically after a user input.
My viewModel:
class NewsArticleListViewModel : BaseViewModel() {
companion object {
private const val pageSize = 20
}
val preferences: SupportPreferences by instance()
private val viewStateLiveData = MutableLiveData<ViewState<Any>>()
private val getNewsListUseCase: GetNewsArticleListUseCase by instance()
private lateinit var dataSource: NewsArticleListPagingSource
private lateinit var brand: String
private var searchText = ""
val newsParamsLiveData = MutableLiveData<NewsArticleListPagingSource.Params>()
val news = Pager(
config = PagingConfig(pageSize = pageSize, enablePlaceholders = true),
pagingSourceFactory = { NewsArticleListPagingSource(getNewsListUseCase, generateParams(), pageSize, viewStateLiveData)
}, initialKey = 1
).liveData.cachedIn(viewModelScope)
// val news = newsParamsLiveData.switchMap { params ->
// Pager(
// PagingConfig(
// pageSize = pageSize,
// enablePlaceholders = true),
// pagingSourceFactory = { NewsArticleListPagingSource(getNewsListUseCase, params, pageSize, viewStateLiveData)}
// ).liveData.cachedIn(viewModelScope)
// }
fun viewState(): LiveData<ViewState<Any>> = viewStateLiveData
fun initialize(brand: String) {
this.brand = brand
dataSource = NewsArticleListPagingSource(getNewsListUseCase, generateParams(), pageSize, viewStateLiveData)
}
fun updateNewsSort(sortFilter: NewsFilter) {
if (preferences.newsArticlesSortKey.value == sortFilter) return
preferences.newsArticlesSortKey.value = sortFilter
dataSource.invalidate()
}
fun refresh(){
dataSource.invalidate()
}
fun search(value: String) {
if (value != searchText) {
searchText = value
}
}
fun updateSelectedAuthor(selectedAuthor: NewsFilter) {
val defaultValue = preferences.newsSelectedAuthor.defaultValue
if (preferences.newsSelectedAuthor.value == selectedAuthor) {
// deselect current selection
if (selectedAuthor == defaultValue) return
preferences.newsSelectedAuthor.value = defaultValue
} else {
preferences.newsSelectedAuthor.value = selectedAuthor
}
}
fun updateSelectedCategory(selectedCategory: NewsFilter) {
val defaultValue = preferences.newsSelectedCategory.defaultValue
if (preferences.newsSelectedCategory.value == selectedCategory) {
// deselect current selection
if (selectedCategory == defaultValue) return
preferences.newsSelectedCategory.value = defaultValue
} else {
preferences.newsSelectedCategory.value = selectedCategory
}
}
private fun generateParams(): NewsArticleListPagingSource.Params {
return NewsArticleListPagingSource.Params(
brand = brand,
isTrending = preferences.newsArticlesSortKey.value.filterValue == NewsArticleListView.SortKey.TRENDING.name,
author = if (preferences.newsSelectedAuthor.value == preferences.newsSelectedAuthor.defaultValue) null else preferences.newsSelectedAuthor.value.filterValue,
category = if (preferences.newsSelectedCategory.value == preferences.newsSelectedCategory.defaultValue) null else preferences.newsSelectedCategory.value.filterValue,
search = searchText
)
}
}
My paging source
class NewsArticleListPagingSource(
private val getNewsArticleListUseCase: GetNewsArticleListUseCase,
private val queryParams: Params,
private val pageSize: Int,
private val viewStateLiveData: MutableLiveData<ViewState<Any>>
): PagingSource<Int, NewsArticleData>() {
var totalPages = 0
var currentPagingSource: NewsArticleListPagingSource? = null
override fun getRefreshKey(state: PagingState<Int, NewsArticleData>): Int? {
return state.anchorPosition?.let { anchorPosition ->
state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1)
?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1)
}
}
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, NewsArticleData> {
val pageIndex = params.key ?: 1
var result = listOf<NewsArticleData>()
return try {
getNewsArticleListUseCase.invoke(GetNewsArticleListUseCase.Params(
queryParams.brand,
pageIndex,
queryParams.category,
queryParams.tag,
queryParams.isTrending,
queryParams.isFeatured,
queryParams.author,
queryParams.search,
queryParams.sort,
pageSize
)).collect {
result = it.data
totalPages = it.totalPages ?: 10
}
LoadResult.Page(
data = result,
prevKey = if (pageIndex == 1) null else pageIndex -1,
nextKey = if (pageIndex >= totalPages) null else pageIndex + 1
)
} catch (e: Exception) {
viewStateLiveData.value = ViewState.Error(true, e)
return LoadResult.Error(e)
}
}
fun updateParams() {
currentPagingSource?.invalidate()
}
data class Params(
val brand: String,
val category: String? = null,
val tag: String? = null,
val isTrending: Boolean = true,
val isFeatured: Boolean? = null,
val author: String? = null,
val search: String? = null,
val sort: String? = null
)
}
My controller:
package au.com.punters.support.android.news.list
import au.com.punters.support.android.content.GetNewsArticleListQuery
import au.com.punters.support.android.data.injection.SupportModules
import au.com.punters.support.android.news.model.NewsArticleData
import au.com.punters.support.android.news.rows.RowNewsArticle
import au.com.punters.support.android.news.rows.RowNewsFilters
import au.com.punters.support.android.preferences.SupportPreferences
import au.com.punters.support.android.view.rows.RowDivider
import com.airbnb.epoxy.EpoxyModel
import com.airbnb.epoxy.paging.PagedListEpoxyController
import com.airbnb.epoxy.paging3.PagingDataEpoxyController
import org.kodein.di.Kodein
import org.kodein.di.KodeinAware
import org.kodein.di.generic.instance
open class NewsArticlePagedListController(protected val listener: NewsArticleListListener)
: PagingDataEpoxyController<NewsArticleData>(), KodeinAware {
override val kodein: Kodein = SupportModules.instance
private val preference: SupportPreferences by instance()
override fun buildItemModel(currentPosition: Int, item: NewsArticleData?): EpoxyModel<*> {
if (item == null) return RowDivider(currentPosition.toString() + "placeholder")
return createRowNewsArticle(item)
}
open fun createRowNewsArticle(item: NewsArticleData): EpoxyModel<*> {
return RowNewsArticle(item,
{ listener.isNewsRestricted(item) },
{ listener.onNewArticleSelected(item, false) })
}
override fun addModels(models: List<EpoxyModel<*>>) {
preference.newsSelectedCategory.value
RowNewsFilters(
currentSorting = preference.newsArticlesSortKey.value.displayName,
currentAuthor = preference.newsSelectedAuthor.value.displayName,
currentCategory = preference.newsSelectedCategory.value.displayName,
onSortClick = { listener.onSortClicked() },
onAuthorClick = { listener.onAuthorFilterClicked() },
onCategoryClick = { listener.onCategoryFilterClicked() },
onSearchTextChanged = { text -> listener.onSearchTextChanged(text) },
onSearchButtonClicked = { listener.onSearchButtonClicked() }
).addTo(this)
super.addModels(models)
}
}
and my paging data is being observed in the fragment
viewModel.news.observe(viewLifecycleOwner) {
lifecycleScope.launch {
newsArticlePagedListController.submitData(it)
}
}
I have tried calling dataSource.invalidate() within the updateSelectAuthor(), search() and updateSelectedCategory but nothing changes. Also tried creating a MutableLiveData of the params which are the values that change and assign it to a switchMap within the construction of the pager but no list will show up.