Flutter package for implementing infinite scroll pagination, with support for pull to refresh.
- Pull down to refresh
- Pull up / scroll to bottom to load more
- Different controller statuses (loading, refreshing, noMoreData, loadingFailed, refreshingFailed etc...)
- Widgets to indicate status (no data loaded, errors when refreshing or loading more, no more items to load etc...)
- Flutter Android
- Flutter iOS
- Linux (NOT TESTED)
- Windows (NOT TESTED)
- MacOS (NOT TESTED)
- Fuschia (NOT TESTED)
Add gamma_smart_pagination: ^1.0.4
to your pubspec.yaml
dependencies. And import it:
import 'package:gamma_smart_pagination/gamma_smart_pagination.dart';
- Simply create a
GammaSmartPagination
widget. - Wrap it around any scrollable widget (ListView / GridView).
- Pass it the required
GammaController
andScrollController
along with any other available params.
Tip: Keep in mind that
GammaSmartPagination
is actually aSingleChildScrollView
, so be mindful to not have unbounded height in the parent widget. (ListViews wrapped with GammaSmartPagination need to haveshrinkWrap:true
andNeverScrollableScrollPhysics
.)
class ExampleApp extends StatefulWidget {
const ExampleApp({super.key});
@override
State<ExampleApp> createState() => _ExampleAppState();
}
class _ExampleAppState extends State<ExampleApp> {
GammaController gammaController = GammaController();
ScrollController scrollController = ScrollController();
List<String> itemsList = [];
bool isLoading = false;
@override
void initState() {
fetchItems();
super.initState();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Gamma Smart Pagination Example',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: const Text('Example screen'),
),
body: SafeArea(
child: isLoading ? _getLoadingIndicator : _buildBody(),
),
),
);
}
Widget _buildBody() {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(
// This is the important part:
child: GammaSmartPagination(
gammaSmartController: gammaController,
scrollController: scrollController,
onLoadMore: () => loadMore(),
onRefresh: () => refreshItems(),
itemCount: itemsList.length,
header: Container(
height: 300.0,
color: Colors.orange,
),
child: ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) => ListTile(
title: Text(itemsList[index]),
),
separatorBuilder: (context, index) => const Divider(),
itemCount: itemsList.length,
),
),
),
],
);
}
Widget get _getLoadingIndicator => const Center(
child: CircularProgressIndicator(),
);
Future<void> fetchItems() async {
setState(() {
isLoading = true;
});
await Future.delayed(const Duration(seconds: 1));
final fakeItems = List.generate(10, (index) => 'Item ${index + 1}');
// Update the controller status to completed or failed based on the result
// If it is the initial request for data, you can use the idle or failed status
gammaController.setIdle();
setState(() {
itemsList = fakeItems;
isLoading = false;
});
}
Future<void> loadMore() async {
await Future.delayed(const Duration(seconds: 1));
final fakeItems = List.generate(10, (index) => 'Item ${itemsList.length + index + 1}');
// Update the controller status to completed or failed based on the result
gammaController.setLoadingCompleted();
setState(() {
itemsList = [...itemsList, ...fakeItems];
});
}
Future<void> refreshItems() async {
await Future.delayed(const Duration(seconds: 1));
final fakeItems = List.generate(10, (index) => 'Item ${index + 1}');
// Update the controller status to completed or failed based on the result
gammaController.setRefreshingCompleted();
setState(() {
itemsList = fakeItems;
});
}
@override
void dispose() {
// Don't forget to dispose the controllers
gammaController.dispose();
scrollController.dispose();
super.dispose();
}
}
For a full example check out the Example app
GammaSmartPagination(
key: const Key('firstScreenInfinitePagination'),
// Required (for status updates)
gammaSmartController: GammaController(),
// Required (for triggering load more when scrolled to bottom)
scrollController: ScrollController(),
// The amount of space by which to trigger reload when scroll reaches the end of the list.
scrollSensitivityTrigger: 200.0,
// Future<void> Callback when pull to refresh is triggered
onRefresh: () => refreshItems(),
// Future<void> Callback when user scrolls to the bottom of the list
onLoadMore: () => loadMore(),
// Required
itemCount: itemsList.length,
// Required (Usually a ListView or GridView)
// IMPORTANT:
// shrinkWrap: true
// physics: const NeverScrollableScrollPhysics(),
//
// This is required because the GammaSmartPagination
// is a SingleChildScrollView, and any nested scrollable
// should be NON-scrollable, and shrink wrap set to true
// where needed
child: ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: itemsList.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(itemsList[index]),
);
}
),
// This is an optional header widget
header: Container(
height: 300.0,
color: Colors.orange,
),
noInitialDataWidget: Text('No data loaded'),
noMoreDataWidget: Text('No more data to load'),
loadingFailedWidget: Text('Failed to load more items...'),
refreshFailedWidget: Text('Failed to refresh data...'),
loadingIndicator: CircularProgressIndicator.adaptive(),
// Set to true if you need to have logs in console
// when loadMore or onRefresh are called
enableLogging: false,
)