How to clear/remove all items in page list adapter - android

I'm using the android paging library to show search result items, is there any way I can clear/remove all the loaded result items, Calling Invalidate on live Paged List refreshing the list not clear/remove items
In Activity:
fun clearSearchResult() {
if (productSearchResultItemAdapter.itemCount > 0) {
viewModel.invalidateResultList()
}
}
In ViewModel
private fun searchProductByTerm(searchTerm: String): Listing<Item> {
sourceFactory = ProductSearchItemDataSourceFactory(productSearchUC, compositeDisposable, searchTerm, resourceResolver)
val livePagedList = LivePagedListBuilder(sourceFactory, pagedListConfig)
//The executor used to fetch additional pages from the DataSource
.setFetchExecutor(getNetworkExecutor())
.build()
return Listing(
pagedList = livePagedList,
networkState = switchMap(sourceFactory.sourceLiveData) {
it.networkState
},
retry = {
sourceFactory.sourceLiveData.value?.retryAllFailed()
}
)
}
fun invalidateResultList() {
sourceFactory?.sourceLiveData?.value?.invalidate()
}
private val productSearchName = MutableLiveData<String>()
private val repoResult = map(productSearchName) {
searchProductByTerm(it)
}

If you're working with PagingDataAdapter, searchAdapter.submitData(lifecycle, PagingData.empty()) works

submitting null clear the currently loaded page list
productSearchResultItemAdapter.submitList(null)

In Java:
I cleared all items on in PagedListAdapter by calling invalidate() on DataSource instance like that
public void clear(){
movieDataSource.invalidate();
}
Add this method in your ViewModel then call it in your activity
movieViewModel.clear();
movieAdapter.notifyDataSetChanged();
Then Load any data you want
You can see how I made it in my project.
Here is the Link: https://github.com/Marwa-Eltayeb/MovieTrailer

In Fragment
lifecycleScope.launch {
viewModel.currentResult = null
viewModel.getSearchAudio(binding.etxtSearch.text.toString().trim(), 0).collectLatest { it ->
Log.v(mTAG, "Status: New record")
adapterAudioList.submitData(it)
}
}
In ViewModel
var currentResult: Flow<PagingData<AudioModel>>? = null
fun getSearchAudio(trackName: String, lastPageCount: Int): Flow<PagingData<AudioModel>> {
val lastResult = currentResult
if (lastResult != null) {
return lastResult
}
val newResult: Flow<PagingData<AudioModel>> = videoRepository.getAudioSearchPaging(trackName, lastPageCount).cachedIn(viewModelScope)
currentResult = newResult
return newResult
}
In videoRepository
fun getAudioSearchPaging(trackName: String, lastPageCount: Int): Flow<PagingData<AudioModel>> {
return Pager(
config = PagingConfig(pageSize = KeyConstants.AUDIO_PAGE_SIZE, enablePlaceholders = false),
pagingSourceFactory = { AudioSearchPagingSource(context, trackName, lastPageCount) },
).flow
}

Before invalidate, clear your list data item.
Like we did in simple way:
list.clear();
adapter.notifyDataSetChanged();

Related

StateFlow collect not firing for list type

#HiltViewModel
class HistoryViewModel #Inject constructor(private val firebaseRepository: FirebaseRepository) :
ViewModel() {
private val translateList: MutableList<Translate> = mutableListOf()
private val _translateListState: MutableStateFlow<List<Translate>> =
MutableStateFlow(translateList)
val translateListState = _translateListState.asStateFlow()
init {
listenToSnapshotData()
}
private suspend fun addItemToList(translate: Translate) {
Log.d("customTag", "item added adapter $translate")
translateList.add(translate)
_translateListState.emit(translateList)
}
private suspend fun removeItemFromList(translate: Translate) {
Log.d("customTag", "item removed adapter $translate")
val indexOfItem = translateList.indexOfFirst {
it.id == translate.id
}
if (indexOfItem != -1) {
translateList.removeAt(indexOfItem)
_translateListState.emit(translateList)
}
}
private suspend fun updateItemFromList(translate: Translate) {
Log.d("customTag", "item modified adapter $translate")
val indexOfItem = translateList.indexOfFirst {
it.id == translate.id
}
if (indexOfItem != -1) {
translateList[indexOfItem] = translate
_translateListState.emit(translateList)
}
}
private fun listenToSnapshotData() {
viewModelScope.launch {
firebaseRepository.translateListSnapshotListener().collect { querySnapshot ->
querySnapshot?.let {
for (document in it.documentChanges) {
val translateData = document.document.toObject(Translate::class.java)
when (document.type) {
DocumentChange.Type.ADDED -> {
addItemToList(translate = translateData)
}
DocumentChange.Type.MODIFIED
-> {
updateItemFromList(translate = translateData)
}
DocumentChange.Type.REMOVED
-> {
removeItemFromList(translate = translateData)
}
}
}
}
}
}
}
}
Here data comes properly in querySnapshot in listenToSnapshotData function. And post that it properly calls corresponding function to update the list.
But after this line _translateListState.emit(translateList) flow doesn't go to corresponding collectLatest
private fun observeSnapShotResponse() {
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
historyViewModel.translateListState.collectLatest {
Log.d("customTag", "calling submitList from fragment")
translateListAdapter.submitList(it)
}
}
}
}
calling submitList from fragment is called once at the start, but as & when data is modified in list viewmodel, callback doesn't come to collectLatest
This is from StateFlow documentation:
Values in state flow are conflated using Any.equals comparison in a similar way to distinctUntilChanged operator. It is used to conflate incoming updates to value in MutableStateFlow and to suppress emission of the values to collectors when new value is equal to the previously emitted one.
You are trying to emit the same instance of List all the time, which has no effect because of what is written in the docs. You will have to create new instance of the list every time.

LoadAppend In Paging library 3 is never called

I want to store in room database by triggering the online source , I have set up a remoteMediator to grab the data and save it into database , upon doing that it only triggers Refresh for first time and it only save the first page items but upon scrolling down , LoadAppen is never triggered not any data is saved , i need some help , Thank you
Mediator Class
#ExperimentalPagingApi
class PopularMediator(
private var movieDatabase: MovieDatabase,
private var moviesDao: MoviesDao,
private var itemsKeyDao: ItemsKeyDao,
private var authService: AuthService
) : RemoteMediator<Int, Result>() {
override suspend fun load(loadType: LoadType, state: PagingState<Int, Result>): MediatorResult {
return try {
val currentPage = when(loadType){
LoadType.REFRESH -> 1
LoadType.PREPEND -> {
return MediatorResult.Success(endOfPaginationReached = true)
}
LoadType.APPEND -> {
val key = movieDatabase.withTransaction {
itemsKeyDao.getAllKeys("Popular").lastOrNull()
}
if(key!!.nextKey == null){
return MediatorResult.Success(endOfPaginationReached = true)
}
key.nextKey
}
}
// GET DATA AND PUSH INTO DATABASE
Log.d("TAG","Current Paging Value $currentPage")
val response = authService.getPopular(Utils.MOVIE_API_KEY,"en-US", page = currentPage!!)
movieDatabase.withTransaction {
if(loadType == LoadType.REFRESH){
itemsKeyDao.deleteAllKeys()
}
response.results.forEach {
it.movieCategory = "Popular"
}
moviesDao.insertMovies(response.results)
response.results.forEach {
itemsKeyDao.insertItems(MovieItemKey(
category = "Popular",
nextKey = it.id,
previousKey = null
))
}
}
MediatorResult.Success(endOfPaginationReached = true)
}catch (ex : Exception){
MediatorResult.Error(ex)
}
}

Groupie RecyclerView OnClick returns empty Item

I am using a room database with live data
Retrieving information from Database
private fun getData(query: String) {
var dataIssueJson: MutableList<DataIssue>
dataViewModel.searchDatabase(query).observe(this, {
dataList = it as MutableList<Data>
data = if (dataList.isNotEmpty())
dataList[0] else
Data()
val gson = Gson()
val dataIssueString =
if (dataList.isNotEmpty()) {
dataList[0].dataIssue
} else ""
val type = object : TypeToken<MutableList<DataIssue>>() {}.type
dataIssueJson = if (dataIssueString.isNotEmpty())
gson.fromJson(dataIssueString, type)
else
mutableListOf()
viewAdapter.clear()
initRecyclerView(dataIssueJson.toDataIssueItem())
val dataStatus = if (dataList.isNotEmpty())
dataList[0].dataStatus
else "Status"
dataViewModel.dataStatus.value = dataStatus
colorStatus(dataStatus)
})
}
Recyclerview
private fun initRecyclerView(dataItem: List<dataIssueItem>) {
binding.recyclerViewDataIssues.apply {
layoutManager = LinearLayoutManager(this#MainActivity)
adapter = viewAdapter.apply {
addAll(botItem)
setOnItemClickListener(onClickItem)
notifyDataSetChanged()
}
scrollToPosition(binding.recyclerViewDataIssues.adapter!!.itemCount - 1)
}
}
private fun List<DataIssue>.toDataIssueItem(): List<DataIssueItem> {
return this.map { DataIssueItem(it) }
}
OnClick
private val onClickItem = OnItemClickListener { item, view ->
if (item is DatatIssueItem) {
Log.d(AppConstants.MAIN_ACTIVITY_TAG, "DataIssue:${item.dataIssue.dataIssue}, date&time: ${item.dataIssue.dateAndTimeOfIssue}")
}
}
The Recyclerview works fine, but when I click on the Recyclerview Item it returns an empty Item and I'm just not sure why
Yeah, I figured it out. The data comes from the Item itself, so make sure to let the data be accessible from the Item.

Paging 3 - Why does my retry footer not call my PagingSource's load method?

I've implemented Paging 3 into my app following a codelab and added a footer with a retry button via withLoadStateHeaderAndFooter:
recycler_view_results.adapter = adapter.withLoadStateHeaderAndFooter(
header = UnsplashLoadStateAdapter { adapter.retry() },
footer = UnsplashLoadStateAdapter { adapter.retry() }
)
When I click the retry button in my footer's ViewHolder, adapter.retry() is indeed called, so the setup there is correct. However, this method never ends up calling my PagingSource's load method as it normally should.
My PagingSource (I checked that the LoadResult.Error is returned correctly in an error case):
class UnsplashPagingSource(
private val unsplashApi: UnsplashApi,
private val query: String
) : PagingSource<Int, UnsplashPhoto>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, UnsplashPhoto> {
val position = params.key ?: UNSPLASH_STARTING_PAGE_INDEX
return try {
val response = unsplashApi.searchPhotos(query, position, params.loadSize)
val photos = response.results
LoadResult.Page(
data = photos,
prevKey = if (position == UNSPLASH_STARTING_PAGE_INDEX) null else position - 1,
nextKey = if (photos.isEmpty()) null else position + 1
)
} catch (exception: IOException) {
return LoadResult.Error(exception)
} catch (exception: HttpException) {
return LoadResult.Error(exception)
}
}
}
My repository:
class UnsplashRepository #Inject constructor(private val unsplashApi: UnsplashApi) {
fun getSearchResultStream(query: String): Flow<PagingData<UnsplashPhoto>> {
return Pager(
config = PagingConfig(
pageSize = NETWORK_PAGE_SIZE,
enablePlaceholders = false
),
pagingSourceFactory = { UnsplashPagingSource(unsplashApi, query) }
).flow
}
companion object {
private const val NETWORK_PAGE_SIZE = 20
}
}
And in my fragment I do this:
private fun searchPhotos(query: String) {
searchJob?.cancel()
searchJob = lifecycleScope.launch {
viewModel.searchPhotos(query).collectLatest {
adapter.submitData(it)
}
}
}
Interestingly the retry button for an empty list works:
retry_button.setOnClickListener {
adapter.retry()
// this works
}
It works now after I updated the paging dependency from '3.0.0-alpha02' to '3.0.0-alpha03'. Looks like this was a bug in the library.
Afterward I also found the corresponding bug report: https://issuetracker.google.com/issues/160194384

Using realm with PublishSubject

I want to map my realm results to an immutable viewmodel, and I want to listen to results changes, so i'm emitting them PublishSubject, However, the data doesn't appear in my recyclerview, until I rotate the device, this issue is fixed when I remove observeOn(AndroidSchedulers.mainThread()).
Repository:
fun notionsChanges(state: Boolean): Observable<Pair<MutableList<Notion>, OrderedCollectionChangeSet?>> {
val notionsChanges = PublishSubject.create<Pair<MutableList<Notion>, OrderedCollectionChangeSet?>>()
val realm = Realm.getDefaultInstance()
val queryResult = realm.where<Notion>()
.equalTo("isArchived", state)
.findAllAsync()
val listener: OrderedRealmCollectionChangeListener<RealmResults<Notion>> = OrderedRealmCollectionChangeListener { realmResults, changeSet ->
if (realmResults.isValid && realmResults.isLoaded) {
val results: MutableList<Notion> = realm.copyFromRealm(realmResults)
notionsChanges.onNext(results to changeSet)
}
}
queryResult.addChangeListener(listener)
notionsChanges.doFinally {
queryResult.removeChangeListener(listener)
closeRealm(realm)
}.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
return notionsChanges
}
in my presenter I use this observable to map the model to a view model, then i show(when subscribe) the data in recyclerview inside a fragment:
private var subscriptions: CompositeDisposable = CompositeDisposable()
override fun onResume() {
super.onResume()
showData()
}
override fun onPause() {
subscriptions.clear()
super.onPause()
}
private fun showData() {
val viewModel = present(idleStates, resources, isIdle)
with(viewModel) {
subscriptions.addAll(
notionsChanges.subscribe(notionsAdapter::handleChanges),
//other subscriptions.
)
}
}
notionsAdapter.handleChanges:
fun handleChanges(collectionChange: Pair<List<NotionCompactViewModel>, OrderedCollectionChangeSet?>) {
val (collection, changeset) = collectionChange
debug("${collection.size}") //correctly prints the actual size of the collection.
replaceAll(collection)
if (changeset == null)
notifyDataSetChanged()
else {
for (change in changeset.changeRanges)
notifyItemRangeChanged(change.startIndex, change.length)
for (insertion in changeset.insertionRanges)
notifyItemRangeInserted(insertion.startIndex, insertion.length)
for (deletion in changeset.deletionRanges)
notifyItemRangeRemoved(deletion.startIndex, deletion.length)
}
}
sorry if the code is unclear.
edit: my onBindViewHolder doesn't get called sometimes(when recyclerview is empty, of course).
Since Realm 5.0, the initial changeset is no longer signaled with changeset == null.
You need to check:
if(changeSet.getState() == State.INITIAL) {
adapter.notifyDataSetChanged()

Categories

Resources