Hexsis Infinity Scroll - BLoC Extension
Zero-boilerplate BLoC/Cubit integration with automatic scroll detection and state management.
Installation
dependencies:
hexsis_infinity_scroll_bloc: ^0.2.2
Complete Example
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hexsis_infinity_scroll_bloc/hexsis_infinity_scroll_bloc.dart';
// 1. Define your cubit (just implement fetchPage!)
class ProductCubit extends InfiniteScrollCubit<Product> {
@override
Future<List<Product>> fetchPage(int page) async {
return await api.getProducts(page: page, limit: 20);
}
}
// 2. Use the widget (that's it!)
class ProductListScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => ProductCubit(),
child: Scaffold(
appBar: AppBar(title: Text('Products')),
body: InfiniteScrollBlocListView<Product, ProductCubit>(
itemBuilder: (context, product, index) {
return ProductCard(product);
},
),
),
);
}
}
That's it! The widget automatically:
- ✅ Detects scroll threshold and calls
cubit.loadMore() - ✅ Shows loading indicator on initial load
- ✅ Shows error widget with retry button
- ✅ Shows empty state when no items
- ✅ Shows loading more indicator at bottom
- ✅ Handles pull-to-refresh
- ✅ Uses smart threshold (500px from bottom for long lists)
GridView Support
InfiniteScrollBlocGridView<Product, ProductCubit>(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 8,
mainAxisSpacing: 8,
),
itemBuilder: (context, product, index) => ProductCard(product),
)
Customization
Custom UI Components
InfiniteScrollBlocListView<Product, ProductCubit>(
itemBuilder: (context, product, index) => ProductCard(product),
// Custom builders
separatorBuilder: (context, index) => Divider(height: 1),
loadingBuilder: (context) => CustomLoadingWidget(),
errorBuilder: (context, error, retry) => CustomErrorWidget(error, retry),
emptyBuilder: (context) => CustomEmptyWidget(),
loadingMoreBuilder: (context) => CustomLoadingMoreWidget(),
)
Configuration
InfiniteScrollBlocListView<Product, ProductCubit>(
itemBuilder: (context, product, index) => ProductCard(product),
config: InfiniteScrollConfig(
threshold: 0.9, // Load at 90% (small lists)
thresholdPixels: 300, // 300px from bottom (large lists)
thresholdBreakpoint: 3000, // Switch point
debounceMilliseconds: 200, // Faster response
enablePullToRefresh: true,
),
)
How It Works
InfiniteScrollCubit
Base cubit that handles all pagination logic:
abstract class InfiniteScrollCubit<T> extends Cubit<InfiniteScrollState<T>> {
// You implement this:
Future<List<T>> fetchPage(int page);
// Already implemented for you:
Future<void> loadPage(int page, {bool isRefresh});
Future<void> refresh();
Future<void> loadMore();
}
State managed automatically:
items- Current listcurrentPage- Page numberhasMore- More items availableisLoading,isLoadingMore,isRefreshingerror,stackTrace
Widget Integration
The widget:
- Creates ScrollController
- Listens to scroll events
- Calculates smart threshold
- Calls
cubit.loadMore()at threshold - Uses BlocBuilder to rebuild on state changes
- Handles all UI states automatically
You write ~5 lines, get full infinite scroll! 🎉
Advanced Usage
Manual Control
// Access cubit directly if needed
BlocBuilder<ProductCubit, InfiniteScrollState<Product>>(
builder: (context, state) {
return Column(
children: [
Text('Loaded ${state.items.length} items'),
if (state.hasMore)
ElevatedButton(
onPressed: () => context.read<ProductCubit>().loadMore(),
child: Text('Load More'),
),
],
);
},
)
Error Handling
class ProductCubit extends InfiniteScrollCubit<Product> {
@override
Future<List<Product>> fetchPage(int page) async {
try {
return await api.getProducts(page);
} catch (e) {
// Error automatically captured and shown in UI
rethrow;
}
}
}
Comparison
Without this package:
// 60+ lines of boilerplate:
// - Manual state class
// - Manual loading states
// - Manual scroll detection
// - Manual threshold calculation
// - Manual error handling
// - Manual UI building
With this package:
// 5 lines total:
class ProductCubit extends InfiniteScrollCubit<Product> {
Future<List<Product>> fetchPage(int page) async => api.getProducts(page);
}
InfiniteScrollBlocListView<Product, ProductCubit>(
itemBuilder: (context, product, index) => ProductCard(product),
)
95% less code! 🚀
Reliability
The cubit ignores fetch completions after it has been closed, preventing late async work from emitting into disposed state. Behavioral tests cover loading, pagination, errors, retries, lifecycle safety, and list/grid widget integration.
License
MIT License - Copyright (c) 2025 Hexsis Enterprise LLC