Seamless Pagination in Flutter with Supabase
Effortless Scrolling, Seamless Data Loading – Master Pagination in Flutter with Supabase!
Introduction
Hello Flutter developers! In this article, I will share how to implement smooth pagination using Supabase in a Flutter application. We’ll focus on a PlayerList
widget that efficiently loads and displays a list of players, complete with pagination and loading states.
By the end of this article, you’ll understand not just the how, but also the why behind each step—so you can implement similar functionality in your own applications with confidence. Let’s dive in!
The code snippets are coming from FUT Maidaan app which is live on Apple App Store and Google Play Store. If you wish, you can check it out.
Structuring the PlayerList
Widget
First, let's look at the structure of our PlayerList
widget:
This widget handles multiple responsibilities:
Managing the current state (loading, success, failure, empty)
Holding a list of players
Handling search queries
Managing pagination state
Providing a callback for loading the next page
Each of these properties plays a key role in ensuring the list behaves smoothly. Let's break them down further.
Handling Different States
Our app can be in one of four states:
Loading: Data is being fetched.
Success: Data has been successfully loaded and displayed.
Failure: An error occurred while fetching data.
Empty: No data was found.
Having a well-defined state management approach ensures that we can display the appropriate UI at all times.
Pagination Control
final VoidCallback? nextPage;
This callback triggers fetching the next batch of players when needed. It prevents unnecessary re-fetching and optimizes performance.
Now that we understand the basic structure, let’s see how we handle scrolling and pagination.
Implementing Scroll-Based Pagination
To implement pagination, we need to track the user's scroll position and determine when to fetch more data.
Setting Up the Scroll Controller
We create a
ScrollController
to monitor the scroll position.We define a
_scrollThreshold
(150 pixels) to determine when to load more data.In
initState
, we attach a listener to track scroll events.In
dispose
, we clean up the controller to prevent memory leaks.
Detecting When to Load More Data
How does this work?
Get Maximum Scroll Extent:
maxScroll
tells us the total scrollable height of the list.Get Current Scroll Position:
currentScroll
gives us the user's current scroll position.Compare with Threshold: If the user is within
_scrollThreshold
pixels from the bottom, we triggernextPage
.
This ensures a seamless loading experience, where new data loads just as the user is about to reach the bottom.
Building the UI Based on State
A well-structured UI should reflect different states effectively.
Each case handles a specific UI scenario:
Loading → Shows a loader.
Empty → Displays a message.
Failure → Returns an empty widget (could be improved by showing an error message).
Success → Builds the player list.
Handling the Success State
If
isPaginating
, we add shimmer placeholders at the end.The scroll controller allows tracking scroll events.
shrinkWrap: true
ensures the list takes only the required space.We use
BouncingScrollPhysics
for smooth scrolling.
This creates a polished, responsive list that handles pagination effectively.
Fetching Data from Supabase
Now, let’s explore how we fetch paginated data using Supabase.
Implementing the topPlayers
Function
How This Works
Pagination Logic: We calculate the range using
page * _itemsPerPage
to fetch only the necessary data.Supabase Query:
.from(TablePlayer.tablePlayer)
: Selects the table..order(...)
: Ensures meaningful sorting (e.g., by rating, name, and creation date)..range(start, end)
: Fetches a limited set of data, optimizing performance.
Error Handling: If an error occurs, we return a
Failure
object to handle it gracefully.
Wrapping Up
By structuring our PlayerList
widget efficiently, we ensure:
Smooth scrolling with pagination
State-driven UI rendering
Efficient data fetching from Supabase
Graceful error handling
This approach not only makes the app more performant but also enhances the user experience by preventing unnecessary reloading.