paging_plus 1.0.0
paging_plus: ^1.0.0 copied to clipboard
Lightweight pagination package. Calculate pages, determine next page to fetch, and handle load-more with optimized data fetching for Flutter & Dart.
Paging Plus
A lightweight and intuitive Dart package for pagination and paging management. Easy-to-use utilities for handling page numbers, item counts, and load more functionality in Flutter and Dart applications.
Features #
- ๐ Page Information: Calculate detailed page information from item counts
- ๐ Smart Pagination: Intelligently determine the next page to fetch
- โก Optimization: Minimize redundant data fetching with optimized pagination
- ๐ Load More: Built-in support for "load more" functionality
- ๐ฏ Type Safe: Built with strong typing and null safety
- ๐งช Well Tested: Comprehensive test coverage
- ๐ Equatable: Built on Equatable for easy comparison
- โก Lightweight: Minimal dependencies, pure Dart implementation
Installation #
Add this to your package's pubspec.yaml file:
dependencies:
paging_plus: ^1.0.0
Then run:
dart pub get
Or with Flutter:
flutter pub get
Usage #
Basic Usage #
Page Information
The Page class represents a single page in a paginated dataset:
import 'package:paging_plus/paging_plus.dart';
// Create a page directly
final page = Page(1, 10, 5); // page 1, 10 items, 5 remaining slots
print('Page ${page.pageNumber} has ${page.count} items');
print('Page size: ${page.pageSize}'); // 15
print('Has remaining: ${page.hasRemaining}'); // true
// Get the last page based on item count
final lastPage = Page.lastOf(25, 10);
print('Last page: ${lastPage.pageNumber}'); // 3
print('Items in page: ${lastPage.count}'); // 5
print('Remaining slots: ${lastPage.remainingsCount}'); // 5
Generate All Pages
Generate a complete list of pages for your dataset:
// Generate all pages for 25 items with page size 10
final pages = Page.getPages(25, 10);
print('Total pages: ${pages.length}'); // 3
for (final page in pages) {
print('Page ${page.pageNumber}: ${page.count} items');
}
// Output:
// Page 1: 10 items
// Page 2: 10 items
// Page 3: 5 items
Pagination #
The Paging class helps determine what to fetch next:
import 'package:paging_plus/paging_plus.dart';
// Calculate next page to fetch
// If you have 0 items, fetch page 1
final paging1 = Paging.next(0, 20);
print('Fetch page ${paging1.pageNumber} with size ${paging1.pageSize}');
// Output: Fetch page 1 with size 20
// If you have 50 items with page size 20, fetch page 3
final paging2 = Paging.next(50, 20);
print('Fetch page ${paging2.pageNumber} with size ${paging2.pageSize}');
// Output: Fetch page 3 with size 20
// If you have 40 items (exactly 2 full pages), fetch page 3
final paging3 = Paging.next(40, 20);
print('Fetch page ${paging3.pageNumber} with size ${paging3.pageSize}');
// Output: Fetch page 3 with size 20
Advanced Pagination Options #
The Paging.next() factory provides advanced options for optimization:
// Basic usage - always refetch last page if it has remaining slots
final basic = Paging.next(25, 10);
print('Page: ${basic.pageNumber}, Size: ${basic.pageSize}');
// Output: Page: 3, Size: 10
// Don't refetch last page if it has remaining slots
final optimized = Paging.next(
25,
10,
false, // fetchLastIfHasRemaining
);
print('Page: ${optimized.pageNumber}, Size: ${optimized.pageSize}');
// Advanced: Set minimum remainings threshold and minimum request size
final advanced = Paging.next(
25,
10,
true, // fetchLastIfHasRemaining
3, // minimumRemainingsToTake - only refetch if >= 3 slots remaining
);
Practical Examples #
Infinite Scroll / Load More
class DataController {
List<Item> items = [];
final int pageSize = 20;
bool isLoading = false;
Future<void> loadMore() async {
if (isLoading) return;
isLoading = true;
// Calculate what page to fetch next
final paging = Paging.next(items.length, pageSize);
print('Fetching page ${paging.pageNumber}...');
// Fetch the data
final newItems = await fetchItems(
page: paging.pageNumber,
pageSize: paging.pageSize,
);
items.addAll(newItems);
isLoading = false;
}
Future<List<Item>> fetchItems({
required int page,
required int pageSize,
}) async {
// Your API call here
return [];
}
}
Pagination UI Helper
class PaginationInfo {
final int currentPage;
final int totalPages;
final int itemsPerPage;
final int totalItems;
final bool hasNextPage;
final bool hasPreviousPage;
factory PaginationInfo.fromItemCount(int totalItems, int pageSize) {
final pages = Page.getPages(totalItems, pageSize);
final latestPage = pages.isNotEmpty ? pages.last : Page(1, 0, pageSize);
return PaginationInfo(
currentPage: latestPage.pageNumber,
totalPages: pages.length,
itemsPerPage: pageSize,
totalItems: totalItems,
hasNextPage: latestPage.hasRemaining || latestPage.pageNumber < pages.length,
hasPreviousPage: latestPage.pageNumber > 1,
);
}
PaginationInfo({
required this.currentPage,
required this.totalPages,
required this.itemsPerPage,
required this.totalItems,
required this.hasNextPage,
required this.hasPreviousPage,
});
}
REST API Pagination
class ApiClient {
Future<PaginatedResponse<T>> fetchPage<T>({
required int currentItemCount,
required int pageSize,
required T Function(Map<String, dynamic>) fromJson,
}) async {
// Calculate next page
final paging = Paging.next(currentItemCount, pageSize);
// Make API call
final response = await http.get(
Uri.parse('https://api.example.com/items')
.replace(queryParameters: {
'page': paging.pageNumber.toString(),
'pageSize': paging.pageSize.toString(),
}),
);
final data = jsonDecode(response.body);
final items = (data['items'] as List)
.map((json) => fromJson(json))
.toList();
return PaginatedResponse(
items: items,
page: paging.pageNumber,
pageSize: paging.pageSize,
hasMore: items.length == paging.pageSize,
);
}
}
class PaginatedResponse<T> {
final List<T> items;
final int page;
final int pageSize;
final bool hasMore;
PaginatedResponse({
required this.items,
required this.page,
required this.pageSize,
required this.hasMore,
});
}
Flutter ListView with Load More
class ItemListView extends StatefulWidget {
@override
_ItemListViewState createState() => _ItemListViewState();
}
class _ItemListViewState extends State<ItemListView> {
final List<Item> items = [];
final int pageSize = 20;
bool isLoading = false;
bool hasMore = true;
@override
void initState() {
super.initState();
loadMore();
}
Future<void> loadMore() async {
if (isLoading || !hasMore) return;
setState(() => isLoading = true);
final paging = Paging.next(items.length, pageSize);
final newItems = await fetchItems(
page: paging.pageNumber,
pageSize: paging.pageSize,
);
setState(() {
items.addAll(newItems);
isLoading = false;
hasMore = newItems.length == pageSize;
});
}
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: items.length + (hasMore ? 1 : 0),
itemBuilder: (context, index) {
if (index == items.length) {
// Load more indicator
loadMore();
return Center(child: CircularProgressIndicator());
}
return ListTile(title: Text(items[index].name));
},
);
}
Future<List<Item>> fetchItems({required int page, required int pageSize}) async {
// Your API call
return [];
}
}
API Reference #
Page Class #
Represents a single page in a paginated dataset.
Constructor
const Page(int pageNumber, int count, int remainingsCount)
Properties
| Property | Type | Description |
|---|---|---|
pageNumber |
int |
The current page number (1-indexed) |
count |
int |
Number of items in this page |
remainingsCount |
int |
Number of remaining slots in this page |
hasRemaining |
bool |
Whether this page has remaining slots |
pageSize |
int |
Total page size (count + remaining) |
currentTotalCount |
int |
Total items up to this page |
expectedTotalCount |
int |
Expected total if remaining slots filled |
Factory Methods
Page.lastOf(int itemCount, int pageSize)
Creates the last page based on total item count.
final page = Page.lastOf(25, 10);
print(page.pageNumber); // 3
print(page.count); // 5
Page.getPages(int itemCount, int pageSize)
Generates a list of all pages for the dataset.
final pages = Page.getPages(25, 10);
print(pages.length); // 3
Paging Class #
Represents a pagination request with optimized page size calculation.
Constructor
const Paging(int pageNumber, int pageSize, [bool shouldHasDuplicates = false])
Properties
| Property | Type | Description |
|---|---|---|
pageNumber |
int |
Page number to fetch (1-indexed) |
pageSize |
int |
Number of items per page |
shouldHasDuplicates |
bool |
Whether duplicates may occur |
Factory Methods
Paging.next()
factory Paging.next(
int itemCount,
int pageSize,
[bool fetchLastIfHasRemaining = true,
int minimumRemainingsToTake = 0]
)
Calculates the next page to fetch with optional optimization.
Parameters:
itemCount- Current total number of itemspageSize- Desired items per pagefetchLastIfHasRemaining- Re-fetch last page if it has remaining slots (default: true)minimumRemainingsToTake- Minimum remaining slots before optimization (default: 0)
Returns: A Paging object specifying the next page to fetch.
// Simple usage
final paging = Paging.next(50, 20);
// With optimization
final optimized = Paging.next(50, 20, false, 5, 10);
Understanding Pagination Optimization #
The Paging.next() method includes an optimization algorithm that can reduce redundant data fetching:
Standard Behavior (default) #
// With 25 items and page size 10:
// Page 1: 10 items, Page 2: 10 items, Page 3: 5 items (5 remaining)
final paging = Paging.next(25, 10); // Default: refetch page 3
// Result: page 3, size 10 (will fetch 5 new items + 5 duplicates)
Optimized Behavior #
// Don't refetch if last page has remaining slots
final paging = Paging.next(25, 10, false);
// Result: Uses GCD algorithm to find optimal page size
// This minimizes duplicate data while filling remaining slots
The optimization uses the Greatest Common Divisor (GCD) algorithm to calculate an efficient page size that:
- Minimizes duplicate data fetching
- Respects minimum request size requirements
- Fills remaining page slots efficiently
Testing #
The package includes comprehensive unit tests covering:
- Page creation and calculations
- Latest page determination
- Page list generation
- Next page calculation
- Pagination optimization
- Edge cases and boundary conditions
- Equatable implementation
Run tests with:
dart test
Or with Flutter:
flutter test
Examples #
For more comprehensive examples, check out the example directory.
To run the examples:
cd example
dart pub get
dart run main.dart
Contributing #
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
License #
This project is licensed under the MIT License - see the LICENSE file for details.
Changelog #
See CHANGELOG.md for a list of changes in each version.
Support #
If you encounter any issues or have questions:
- Check the API Reference section
- Look at the examples
- Open an issue on GitHub
Made with โค๏ธ for the Dart and Flutter community