A modern, efficient, and state-management agnostic pagination library for Flutter applications. Pagist provides a clean abstraction for handling paginated data with automatic loading, error handling, and refresh capabilities.
- π Automatic pagination: Seamlessly loads more data as users scroll
- π Pull-to-refresh: Built-in refresh functionality
- π― State management agnostic: Works with any state management solution
- π‘οΈ Type-safe: Full TypeScript-like generics support
- π¨ Customizable UI: Flexible widget customization for all states
- β‘ Performance optimized: Efficient memory usage and smooth scrolling
- π§ͺ Well tested: Comprehensive test coverage
- π± Production ready: Battle-tested in real applications
Add pagist to your pubspec.yaml:
dependencies:
pagist: ^0.0.1Then run:
flutter pub getimport 'package:pagist/pagist.dart';Here's a simple example showing how to implement pagination for a list of users:
import 'package:flutter/material.dart';
import 'package:pagist/pagist.dart';
// 1. Define your data model
class User {
final int id;
final String name;
final String email;
User({required this.id, required this.name, required this.email});
}
// 2. Implement PagingSource
class UserPagingSource extends PagingSource<int, User> {
@override
Future<LoadResult<int, User>> load(LoadParams<int> params) async {
final page = params.key ?? 1;
final users = await fetchUsersFromApi(page, params.loadSize);
return LoadResult<int, User>(
data: users,
nextKey: users.isNotEmpty ? page + 1 : null,
);
}
Future<List<User>> fetchUsersFromApi(int page, int pageSize) async {
// Your API call implementation
// ...
}
}
// 3. Use in your widget
class UserListPage extends StatefulWidget {
@override
_UserListPageState createState() => _UserListPageState();
}
class _UserListPageState extends State<UserListPage> {
late PaginationController<int, User> _controller;
@override
void initState() {
super.initState();
_controller = PaginationController<int, User>(
pagingSource: UserPagingSource(),
pageSize: 20,
initialKey: 1,
);
_controller.loadMore(); // Load initial data
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Users')),
body: PaginatedListView<User>(
controller: _controller,
itemBuilder: (context, user, index) {
return ListTile(
title: Text(user.name),
subtitle: Text(user.email),
);
},
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}PaginatedListView<User>(
controller: _controller,
enableRefresh: true,
loadMoreThreshold: 200.0, // Load more when 200px from bottom
itemBuilder: (context, user, index) {
return UserCard(user: user);
},
loadingWidget: Center(
child: CircularProgressIndicator(),
),
errorWidget: ErrorRetryWidget(
onRetry: () => _controller.refresh(),
),
emptyWidget: EmptyStateWidget(),
loadingMoreWidget: Padding(
padding: EdgeInsets.all(16),
child: Center(child: CircularProgressIndicator()),
),
)Pagist works seamlessly with any state management solution. Here's an example with Provider:
class UserListNotifier extends ChangeNotifier {
late PaginationController<int, User> _controller;
UserListNotifier() {
_controller = PaginationController<int, User>(
pagingSource: UserPagingSource(),
pageSize: 20,
);
// Listen to pagination state changes
_controller.stateStream.listen((_) => notifyListeners());
}
PaginationState<User> get state => _controller.currentState;
void loadMore() => _controller.loadMore();
void refresh() => _controller.refresh();
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}Pagist follows a clean architecture pattern with clear separation of concerns:
PagingSource<Key, Value>: Abstract class defining how to load dataPaginationController<Key, Value>: Manages pagination state and loading logicPaginatedListView<T>: Flutter widget that displays paginated dataLoadParams<Key>: Parameters passed to the paging sourceLoadResult<Key, Value>: Result returned by the paging sourcePaginationState<Value>: Current state of pagination
βββββββββββββββββββ ββββββββββββββββββββ βββββββββββββββββββ
β PagingSource ββββββPaginationControllerββββββPaginatedListViewβ
βββββββββββββββββββ ββββββββββββββββββββ βββββββββββββββββββ
β β β
β β β
βΌ βΌ βΌ
Load Data Manage State Display UI
abstract class PagingSource<Key, Value> {
Future<LoadResult<Key, Value>> load(LoadParams<Key> params);
}class PaginationController<Key, Value> {
PaginationController({
required PagingSource<Key, Value> pagingSource,
int pageSize = 20,
Key? initialKey,
});
Stream<PaginationState<Value>> get stateStream;
PaginationState<Value> get currentState;
Future<void> loadMore();
Future<void> refresh();
void dispose();
}class PaginatedListView<T> extends StatefulWidget {
const PaginatedListView({
required this.controller,
required this.itemBuilder,
this.loadingWidget,
this.errorWidget,
this.emptyWidget,
this.loadingMoreWidget,
this.loadMoreThreshold = 200.0,
this.enableRefresh = true,
});
}Pagist is designed to be easily testable. Here's how to test your pagination logic:
void main() {
group('UserPagingSource', () {
late UserPagingSource pagingSource;
setUp(() {
pagingSource = UserPagingSource();
});
test('should load first page correctly', () async {
final result = await pagingSource.load(
LoadParams<int>(key: 1, loadSize: 10),
);
expect(result.data.length, 10);
expect(result.nextKey, 2);
});
});
}We welcome contributions! Please see our Contributing Guide for details.
- Fork the repository
- Clone your fork:
git clone https://github.com/yourusername/pagist.git - Install dependencies:
flutter pub get - Run tests:
flutter test - Create a branch:
git checkout -b feature/your-feature - Make your changes and add tests
- Run tests:
flutter test - Submit a pull request
This project is licensed under the MIT License - see the LICENSE file for details.
- π Documentation
- π Issue Tracker
- π¬ Discussions
Give a βοΈ if this project helped you!