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  Get Familiar With Wear OS 3 (Without A Physical Device)

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  Google I/O 2019 | Build Bigger, Better: Gradle for Large Projects

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  Android Dev Summit 2019 | The Digital Wellbeing Opportunity

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
aster-cloud-sms-pexels-tim-samuel-6697306
View Post
  • Programming
  • Software

Send SMS texts with Amazon’s SNS simple notification service

  • July 1, 2025
aster-cloud-website-pexels-goumbik-574069
View Post
  • Programming
  • Software

Host a static website on AWS with Amazon S3 and Route 53

  • June 27, 2025
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

Stay Connected!
LATEST
  • 1
    Expectations vs. Reality: The AI We Thought We’d Have in 10 Years
    • June 19, 2026
  • digital-nomad-freelancer-worker-2151205464 2
    One paperwork problem – Get your Digital Nomad Visa employment documents fast from UK, EU or Singapore
    • June 16, 2026
  • 3
    Samsung Art Store Brings Art Basel to Homes Worldwide With New Curated Collection
    • June 15, 2026
  • 4
    You Do Not Need to Invest in the IPO of SpaceX, Anthropic, and OpenAI
    • June 10, 2026
  • 5
    The consequences of relying on AI for accurate news
    • June 10, 2026
  • 6
    Connecting AI agents with unstructured data using Google Cloud Storage MCP Servers
    • June 10, 2026
  • 7
    WWDC26: Apple unveils next generation of Apple Intelligence, Siri AI, powerful parental controls, and an expansive set of software improvements
    • June 8, 2026
  • 8
    IBM and Google Cloud Announce Strategic Partnership to Scale AI with Human Expertise and AI‑Powered Delivery
    • June 4, 2026
  • Data center 9
    Data Sovereignty in Spain. It’s Not Just About the Law, It’s About Efficiency
    • June 3, 2026
  • 10
    Ink vs Pixels. What you miss versus what you are actually missing.
    • June 1, 2026
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
  • 1
    Banks race to patch new cyber vulnerabilities, and other cybersecurity news
    • May 25, 2026
  • pope-leo-xiv-cq5dam-1500.844 2
    Pope Leo XIV to Publish First Encyclical on Artificial Intelligence and Human Dignity on 25 May
    • May 22, 2026
  • 3
    Portfolio to Clients, and is Strengthened by Ongoing Project Glasswing Work
    • May 20, 2026
  • reMarkable Paper Pure 4
    Everything The reMarkable Paper Pure Actually Does
    • May 14, 2026
  • 5
    Scaling cloud and AI: Microsoft Azure’s commitment to Europe’s digital future
    • May 11, 2026
  • /
  • Technology
  • Tools
  • About
  • Contact Us

Input your search keywords and press Enter.