aster.cloud aster.cloud
  • /
  • Platforms
    • Public Cloud
    • On-Premise
    • Hybrid Cloud
    • Data
  • Architecture
    • Design
    • Solutions
    • Enterprise
  • Engineering
    • Automation
    • Software Engineering
    • Project Management
    • DevOps
  • Programming
    • Learning
  • Tools
  • About
  • /
  • Platforms
    • Public Cloud
    • On-Premise
    • Hybrid Cloud
    • Data
  • Architecture
    • Design
    • Solutions
    • Enterprise
  • Engineering
    • Automation
    • Software Engineering
    • Project Management
    • DevOps
  • Programming
    • Learning
  • Tools
  • About
aster.cloud aster.cloud
  • /
  • Platforms
    • Public Cloud
    • On-Premise
    • Hybrid Cloud
    • Data
  • Architecture
    • Design
    • Solutions
    • Enterprise
  • Engineering
    • Automation
    • Software Engineering
    • Project Management
    • DevOps
  • Programming
    • Learning
  • Tools
  • About
  • Programming

Fetching Data And Binding It To The UI In The MAD Skills Series

  • aster.cloud
  • October 12, 2021
  • 4 minute read

Welcome back to the Paging 3.0 MAD Skills series! In the previous article, we went over the Paging library, saw how it fits into the app’s architecture, and we integrated it into the data layer of the app. We did this using a PagingSource to fetch our data, and then used it, along with a PagingConfig to create a Pager object that provided a Flow<PagingData> for UI consumption. In this article I’ll be covering how to actually consume the Flow<PagingData> in your UI.

Preparing PagingData for the UI

The app currently has the ViewModel exposing the information needed to render the UI in the UiState data class, which contains a searchResult field; an in-memory cache for result searches that survives configuration changes.


Partner with aster.cloud
for your next big idea.
Let us know here.



From our partners:

CITI.IO :: Business. Institutions. Society. Global Political Economy.
CYBERPOGO.COM :: For the Arts, Sciences, and Technology.
DADAHACKS.COM :: Parenting For The Rest Of Us.
ZEDISTA.COM :: Entertainment. Sports. Culture. Escape.
TAKUMAKU.COM :: For The Hearth And Home.
ASTER.CLOUD :: From The Cloud And Beyond.
LIWAIWAI.COM :: Intelligence, Inside and Outside.
GLOBALCLOUDPLATFORMS.COM :: For The World's Computing Needs.
FIREGULAMAN.COM :: For The Fire In The Belly Of The Coder.
ASTERCASTER.COM :: Supra Astra. Beyond The Stars.
BARTDAY.COM :: Prosperity For Everyone.

data class UiState(
    val query: String,
    val searchResult: RepoSearchResult
)

sealed class RepoSearchResult {
    data class Success(val data: List<Repo>) : RepoSearchResult()
    data class Error(val error: Exception) : RepoSearchResult()
}
Initial UiState Definition

With Paging 3.0, we drop the searchResult val from the UiState, opting instead to replace it with a Flow of PagingData<Repo> exposed separately from the UiState. This new Flow will serve the same purpose as the searchResult: provide a list of items to be rendered by the UI.

A private method “searchRepo()” is added to the ViewModel, which calls the Repository to provide a PagingData Flow from the Pager. We can then call this method to create our Flow<PagingData<Repo>> based on the search terms the user enters. We also make use of the cachedIn operator on the resulting PagingData Flow which caches it for quicker reuse using the ViewModelScope.

class SearchRepositoriesViewModel(
    private val repository: GithubRepository,
    …
) : ViewModel() {
    …
    private fun searchRepo(queryString: String): Flow<PagingData<Repo>> =
        repository.getSearchResultStream(queryString)
}
PagingData Flow integration from the repository

It’s important to expose the <strong class="hy kk">PagingData</strong> <strong class="hy kk">Flow</strong> independent of other <strong class="hy kk">Flows</strong>. This is because the PagingData itself is a mutable type, and maintains its own internal stream of data that updates over time.

Read More  Google I/O 2019 | What's New in Android Studio UI Design and Debugging Tools

With the Flows that comprise the fields of the UiState fully defined, we can combine them into a StateFlow of UiState, which can then be exposed to, and consumed by the UI alongside the Flow of PagingData. With the above, we are now ready to start consuming our Flows in the UI.

class SearchRepositoriesViewModel(
    …
) : ViewModel() {

    val state: StateFlow<UiState>

    val pagingDataFlow: Flow<PagingData<Repo>>
    
    init {
        …

        pagingDataFlow = searches
            .flatMapLatest { searchRepo(queryString = it.query) }
            .cachedIn(viewModelScope)

        state = combine(...)
    }

}
Exposing the PagingData Flow to the UI. Note the use of the cachedIn operator

Consuming PagingData in the UI

The first thing we do is switch the RecyclerView Adapter from a ListAdapter to a PagingDataAdapter. A PagingDataAdapter is a RecyclerView Adapter optimized for diffing and aggregating updates from PagingData to make sure changes in the backing data set are propagated as efficiently as possible.

// Before
// class ReposAdapter : ListAdapter<Repo, RepoViewHolder>(REPO_COMPARATOR) {
//     …
// }

// After
class ReposAdapter : PagingDataAdapter<Repo, RepoViewHolder>(REPO_COMPARATOR) {
    …
}
Switching from a ListAdapter to a PagingDataAdapter

Next, we start to collect from the PagingData Flow, so we can bind its emissions to the PagingDataAdapter using the submitData suspending function.

   private fun ActivitySearchRepositoriesBinding.bindList(
        …
        pagingData: Flow<PagingData<Repo>>,
    ) {
        …
        lifecycleScope.launch {
            pagingData.collectLatest(repoAdapter::submitData)
        }

    }
Consuming PagingData with a PagingDataAdapter. Note the use of the collectLatest operator

Also, as a user experience perk, we want to make sure that when the user searches for something new, they are taken to the top of the list to show the first search results. We want to do this when we’re confident when we’ve finished loading and have presented data in the UI. We achieve this by taking advantage of the loadStateFlow exposed by the PagingDataAdapter and the “hasNotScrolledForCurrentSearch’’ field in the UiState used for tracking if the user has manually scrolled the list themselves. The combination of these two creates a flag to let us know if we can trigger an auto scroll.

Read More  5 Years Of Kotlin On Android — The Extended Interviews

Since the load states provided by the loadStateFlow are synchronous with what is displayed in the UI, we can confidently scroll to the top of the list once the load state flow notifies us we are not loading for each new query.

   private fun ActivitySearchRepositoriesBinding.bindList(
        repoAdapter: ReposAdapter,
        uiState: StateFlow<UiState>,
        pagingData: Flow<PagingData<Repo>>,
        …
    ) {
        …
        val notLoading = repoAdapter.loadStateFlow
            // Only emit when REFRESH LoadState for the paging source changes.
            .distinctUntilChangedBy { it.source.refresh }
            // Only react to cases where REFRESH completes i.e., NotLoading.
            .map { it.source.refresh is LoadState.NotLoading }

        val hasNotScrolledForCurrentSearch = uiState
            .map { it.hasNotScrolledForCurrentSearch }
            .distinctUntilChanged()

        val shouldScrollToTop = combine(
            notLoading,
            hasNotScrolledForCurrentSearch,
            Boolean::and
        )
            .distinctUntilChanged()

        lifecycleScope.launch {
            shouldScrollToTop.collect { shouldScroll ->
                if (shouldScroll) list.scrollToPosition(0)
            }
        }
    }
Implementing auto scroll to the top for new queries

Adding Headers and Footers

Another advantage of the Paging Library is the ability to display progress indicators either at the top or bottom of the list with the help of the LoadStateAdapter. This implementation of a RecyclerView.Adapter is automatically notified of changes in the Pager as it loads data which enables it to insert items at the top or bottom of the list as needed.

The best part is you don’t even need to change your existing PagingDataAdapter. The withLoadStateHeaderAndFooter extension conveniently wraps your existing PagingDataAdapter with both the header and footer!

   private fun ActivitySearchRepositoriesBinding.bindState(
        uiState: StateFlow<UiState>,
        pagingData: Flow<PagingData<Repo>>,
        uiActions: (UiAction) -> Unit
    ) {
        val repoAdapter = ReposAdapter()
        list.adapter = repoAdapter.withLoadStateHeaderAndFooter(
            header = ReposLoadStateAdapter { repoAdapter.retry() },
            footer = ReposLoadStateAdapter { repoAdapter.retry() }
        )
    }
Headers and Footers

The arguments of the withLoadStateHeaderAndFooter function take definitions of LoadStateAdapters for both the header and footer. The LoadStateAdapter’s in turn host their own ViewHolders, which are bound with the latest load state making it easy to define the behavior of the views. We can even pass arguments to let us retry loading in case there’s an error, which I’ll cover more in the next article.

Read More  How-To: Install and Configure MongoDB Community on Ubuntu 20.04

Next Up

With that we’ve bound our paging data to the UI! For a quick recap, we:

  • Integrated our Paging in the UI layer using the PagingDataAdapter
  • Used the LoadStateFlow exposed by the PagingDataAdapter to guarantee we only auto scroll to the top of the list when the Pager is done loading
  • Used the withLoadStateHeaderAndFooter() to add progress bars to UI when fetching data

Thanks for reading along! Stay tuned, and see you in the next one where we’ll be looking at Paging from the database as a single source of truth, and taking a closer look at the LoadStateFlow!

By TJ
Source Medium


For enquiries, product placements, sponsorships, and collaborations, connect with us at [email protected]. We'd love to hear from you!

Our humans need coffee too! Your support is highly appreciated, thank you!

aster.cloud

Related Topics
  • Android
  • MAD
  • Medium
  • RecyclerView
You May Also Like
View Post
  • Architecture
  • Data
  • Engineering
  • People
  • Programming
  • Software Engineering
  • Technology
  • Work & Jobs

Predictions: Top 25 Careers Likely In High Demand In The Future

  • June 6, 2023
View Post
  • Programming
  • Software Engineering
  • Technology

Build a Python App to Alert You When Asteroids Are Close to Earth

  • May 22, 2023
View Post
  • Programming

Illuminating Interactions: Visual State In Jetpack Compose

  • May 20, 2023
View Post
  • Computing
  • Data
  • Programming
  • Software
  • Software Engineering

The Top 10 Data Interchange Or Data Exchange Format Used Today

  • May 11, 2023
View Post
  • Architecture
  • Programming
  • Public Cloud

From Receipts To Riches: Save Money W/ Google Cloud & Supermarket Bills – Part 1

  • May 8, 2023
View Post
  • Programming
  • Public Cloud

3 New Ways To Authorize Users To Your Private Workloads On Cloud Run

  • May 4, 2023
View Post
  • Programming
  • Public Cloud

Buffer HTTP Requests With Cloud Tasks

  • May 4, 2023
View Post
  • Programming
  • Public Cloud
  • Software
  • Software Engineering

Learn About Google Cloud’s Updated Renderer For The Maps SDK For Android

  • May 4, 2023

Stay Connected!
LATEST
  • 1
    Just make it scale: An Aurora DSQL story
    • May 29, 2025
  • 2
    Reliance on US tech providers is making IT leaders skittish
    • May 28, 2025
  • Examine the 4 types of edge computing, with examples
    • May 28, 2025
  • AI and private cloud: 2 lessons from Dell Tech World 2025
    • May 28, 2025
  • 5
    TD Synnex named as UK distributor for Cohesity
    • May 28, 2025
  • Weigh these 6 enterprise advantages of storage as a service
    • May 28, 2025
  • 7
    Broadcom’s ‘harsh’ VMware contracts are costing customers up to 1,500% more
    • May 28, 2025
  • 8
    Pulsant targets partner diversity with new IaaS solution
    • May 23, 2025
  • 9
    Growing AI workloads are causing hybrid cloud headaches
    • May 23, 2025
  • Gemma 3n 10
    Announcing Gemma 3n preview: powerful, efficient, mobile-first AI
    • May 22, 2025
about
Hello World!

We are aster.cloud. We’re created by programmers for programmers.

Our site aims to provide guides, programming tips, reviews, and interesting materials for tech people and those who want to learn in general.

We would like to hear from you.

If you have any feedback, enquiries, or sponsorship request, kindly reach out to us at:

[email protected]
Most Popular
  • Understand how Windows Server 2025 PAYG licensing works
    • May 20, 2025
  • By the numbers: How upskilling fills the IT skills gap
    • May 21, 2025
  • 3
    Cloud adoption isn’t all it’s cut out to be as enterprises report growing dissatisfaction
    • May 15, 2025
  • 4
    Hybrid cloud is complicated – Red Hat’s new AI assistant wants to solve that
    • May 20, 2025
  • 5
    Google is getting serious on cloud sovereignty
    • May 22, 2025
  • /
  • Technology
  • Tools
  • About
  • Contact Us

Input your search keywords and press Enter.