What is Lazy Loading with BLoC?
Lazy loading in BLoC refers to loading data incrementally as the user scrolls through a list, rather than loading all data at once. This pattern, often called pagination or infinite scroll, improves performance, reduces initial load times, and saves bandwidth. The BLoC pattern helps manage the complex state of loading, error, and pagination metadata (page number, offset, hasMore) in a clean and testable way.
Why Use Lazy Loading?
- Performance – Load only what the user needs, reducing memory usage.
- Faster Initial Load – Display first page quickly, then load more on demand.
- Bandwidth Efficiency – Fetch data in chunks, ideal for mobile networks.
- Better UX – Smooth infinite scrolling without waiting for all data.
- Scalability – Handle large datasets without overwhelming the UI.
Core Concepts
- Pagination Metadata – Tracks current page, offset, limit, and whether more data exists.
- Loading States – Distinguish between initial loading and loading more.
- Error Handling – Allow retry for failed page loads.
- Throttling – Prevent duplicate requests when scrolling triggers multiple events.
Setting Up Dependencies
Add the required packages to pubspec.yaml:
Defining State and Events
Create a state that holds the list of items, pagination metadata, and loading/error flags. Events will trigger initial load and subsequent page loads.
Implementing the BLoC with Throttling
Use a transformer to throttle the FetchMorePosts events. This prevents multiple rapid requests when the user scrolls fast. Also handle initial load and pagination logic.
Repository with Pagination
The repository abstracts the data source. It can fetch from an API, database, or both.
UI with Infinite Scroll
Use ListView.builder with a ScrollController to detect when the user reaches the end. Dispatch FetchMorePosts when near the bottom.
Advanced: Cursor-Based Pagination
Some APIs use cursor-based pagination (e.g., GraphQL, REST with next token). Adapt the state to hold a nextCursor instead of a page number.
Best Practices
- Use throttling – Prevent duplicate
FetchMoreevents when the user scrolls quickly. - Separate initial load from load more – Use different events or a flag to differentiate states.
- Show loading indicators – Provide visual feedback when loading more (e.g., a progress indicator at the bottom).
- Handle errors gracefully – Allow retry by dispatching the event again or showing a retry button.
- Cache pages locally – Consider storing fetched data in a local database to avoid re-fetching.
- Use
Equatablefor states – Prevent unnecessary rebuilds. - Avoid duplicate requests – Check
hasReachedMaxandstatusbefore triggering new loads. - Dispose controllers – Always dispose
ScrollControllerinState.dispose.
Common Mistakes
- ❌ Not checking if already loading – Leads to multiple simultaneous requests.
- ❌ Forgetting to reset pagination on refresh – After refresh, reset
pageandhasReachedMax. - ❌ Using
BlocBuilderwithout optimization – Rebuilds the entire list on each state change, causing flicker. UseBlocSelectorfor parts that change. - ❌ Not handling empty or
hasReachedMaxcases – The UI may keep trying to load more. - ❌ Blocking the UI with heavy computations – Offload parsing to isolates if needed.
- ❌ Not using
constfor item widgets – Improves performance for long lists.
Conclusion
Lazy loading with BLoC is a robust solution for handling paginated data. By separating concerns into events, states, and repositories, you can build performant infinite scroll lists with clear loading and error states. Remember to throttle events, manage pagination metadata carefully, and optimize UI rebuilds. This pattern scales well from simple lists to complex data grids.