How to show empty view while using Android Paging 3 library
Asked Answered
G

4

23

I am using Paging 3 lib. and i am able to check if refresh state is "Loading" or "Error" but i am not sure how to check "empty" state.I am able to add following condition but i am not sure if its proper condition

adapter.loadStateFlow.collectLatest { loadStates ->
                viewBinding.sflLoadingView.setVisibility(loadStates.refresh is LoadState.Loading)
                viewBinding.llErrorView.setVisibility(loadStates.refresh is LoadState.Error)
                viewBinding.button.setOnClickListener { pagingAdapter.refresh() }

                if(loadStates.refresh is LoadState.NotLoading && (viewBinding.recyclerView.adapter as ConcatAdapter).itemCount == 0){
                    viewBinding.llEmptyView.setVisibility(true)
                }else{
                    viewBinding.llEmptyView.setVisibility(false)
                }
            } 

Also I am running into other problem I have implemented search functionality and until more than 2 characters are entered i am using same paging source like following but the above loadstate callback is executed only once.So thats why i am not able to hide empty view if search query is cleared.I am doing so to save api call from front end.

private val originalList : LiveData<PagingData<ModelResponse>> = Transformations.switchMap(liveData){
        repository.fetchSearchResults("").cachedIn(viewModelScope)
    }

    val list : LiveData<LiveData<PagingData<ModelResponse>>> = Transformations.switchMap{ query ->
        if(query != null) {
            if (query.length >= 2)
                repository.fetchSearchResults(query)
            else
                originalList
        }else
            liveData { emptyList<ModelResponse>() }
    } 
Grassland answered 15/10, 2020 at 11:36 Comment(5)
What does the implementation of your repository.fetchSearchResults(query) look like and when are you calling submitData? How are you verifying that the callback is only called once? You should be receiving a new CombinedLoadStates anytime any of the states change (including append / prepend). To be clear, your condition checks itemCount of ConcatAdapter, but I think you want itemCount of just the PagingDataAdapter, since ConcatAdapter will include other adapters (such as LoadStateAdapter) as well. FYI: You can use submitData(PagingData.empty()) to clear the list.Weakly
fun fetchSearchResults(searchQuery : String): LiveData<PagingData<ModelResponse>> { return Pager( config = PagingConfig(pageSize = DEFAULT_PAGE_SIZE, enablePlaceholders = false), pagingSourceFactory = { ModelPagingSource(searchQuery) } ).liveData }Grassland
@Weakly And regarding callback issue, in normal condition it is working fine. I am trying to reuse same pagingsource (originalList) if query length is less than 2 and after reusing the same obj(originalList), loadstate callback method is not executed and i think thats the expected behaviour that for one paging source, refresh loadstate will be called only till "error" or "notloading" state is achieved and will not be called again untill we make some explicit callsGrassland
And can you please explain how to check empty state in listGrassland
You're right that re-using PagingSource will cause issues. I would try to filter the search input instead so that you don't end up reloading when you don't want to instead of trying to "reuse" a PagingSource. For checking empty state, PagingDataAdapter.itemCount will work when called from loadStateFlow / listeners (load state events are guaranteed to be sent synchronously with insert events, so you can be sure the loaded page has been presented). There is also a .snapshot() method in case you need to view the actual items presented themselves.Weakly
D
67

Here is the proper way of handling the empty view in Android Paging 3:

adapter.addLoadStateListener { loadState ->
            if (loadState.source.refresh is LoadState.NotLoading && loadState.append.endOfPaginationReached && adapter.itemCount < 1) {
                recycleView?.isVisible = false
                emptyView?.isVisible = true
            } else {
                recycleView?.isVisible = true
                emptyView?.isVisible = false
            }
        }
Daly answered 21/10, 2020 at 10:26 Comment(5)
what if there are several pages are still to be fetched? For that endOfPaginationReached will return false, that will cause emptyView to be displayed even if data is there. Correct me if i am wrong.Pecos
Always return endOfPaginationReached "false"Eleni
For me loadState.source.refresh is LoadState.NotLoading && loadState.append.endOfPaginationReached seems to be enoughMadisonmadlen
@Gulzar Bhat, vai what will be the logic to show Initial loader? I mean , to show a Lottie animation at the first network call?Rainband
Why do I have to check zero as adapter.itemCount < 1? I think it's okay to check with adapter.itemCount == 0. Is there any difference?Gilmagilman
V
8
adapter.loadStateFlow.collect {
    if (it.append is LoadState.NotLoading && it.append.endOfPaginationReached) {
        emptyState.isVisible = adapter.itemCount < 1
    }
}

The logic is,If the append has finished (it.append is LoadState.NotLoading && it.append.endOfPaginationReached == true), and our adapter items count is zero (adapter.itemCount < 1), means there is nothing to show, so we show the empty state.

PS: for initial loading you can find out more at this answer: https://mcmap.net/q/584134/-paging-3-initial-loading-not-shown

Verticillate answered 23/5, 2021 at 15:22 Comment(0)
A
3

I am using this way and it works.

EDITED: dataRefreshFlow is deprecated in Version 3.0.0-alpha10, we should use loadStateFlow now.

  viewLifecycleOwner.lifecycleScope.launch {
            transactionAdapter.loadStateFlow
                .collectLatest {
                    if(it.refresh is LoadState.NotLoading){
                        binding.textNoTransaction.isVisible = transactionAdapter.itemCount<1
                    }
                }
        }

For detailed explanation and usage of loadStateFlow, please check https://developer.android.com/topic/libraries/architecture/paging/v3-paged-data#load-state-listener

Ambrosia answered 11/3, 2021 at 13:45 Comment(0)
M
2

If you are using paging 3 with Jetpack compose, I had a similar situation where I wanted to show a "no results" screen with the Pager 3 library. I wasn't sure what the best approach was in Compose but I use now this extension function on LazyPagingItems. It checks if there are no items and makes sure there are no items coming by checking if endOfPaginationReached is true.

private val <T : Any> LazyPagingItems<T>.noItems
    get() = loadState.append.endOfPaginationReached && itemCount == 0

Example usage is as follows:

@Composable
private fun ArticlesOverviewScreen(screenContent: Content) {
    val lazyBoughtArticles = screenContent.articles.collectAsLazyPagingItems()
    
    when {
        lazyBoughtArticles.noItems -> NoArticlesScreen()
        else -> ArticlesScreen(lazyBoughtArticles)
    }
}

Clean and simple :).

Maihem answered 6/12, 2022 at 7:54 Comment(7)
Can you describe more?Fixed
I've described a bit more.Maihem
Do you put the private val code in the ViewModel, and after collecting the lazypagingitems in the composable, it says .noItems is an unresolved reference. What can I do to fix this? Thanks.Kellam
@RajNarayanan, I use the extension function in my Composable so the code is not in my ViewModel but in the file containing the relevant Composables.Maihem
@Maihem The extension function is unusable in a composable. It throws multiple unresolved reference errors which I cannot resolve.Kellam
I think your problem might be somewhere else in your code. I can use it without any problems. Are you sure that you have this piece code in the same module and file where your composable is? And did you check your dependencies? Using this requires a dependency for compose: "androidx.paging:paging-compose"?Maihem
@Maihem I placed the extension outside of the composable but inside the same file as the composable, right above the @Composable declaration. It's working now. And I tried to edit your answer to show where exactly the extension is placed, but it said the edit que was full, and I wasn't able to submit it.Kellam

© 2022 - 2024 — McMap. All rights reserved.