Pagination part 2
Presentation (Integration)
Summary
In this lesson, the instructor modifies the Clean Architecture layers to support pagination for the featured books list. The core problem addressed is that the application initially only retrieves the first ten books, making scroll-based data fetching impossible. To resolve this, the HomeRepo, HomeRemoteDataSource, UseCase, and Cubit are updated to accept a pageNumber argument. This semantic page index is then multiplied by ten within the data source to calculate the correct startIndex offset required by the Google Books API. The architectural pipeline is now prepared to handle pagination events, which will be triggered by UI scrolling in the subsequent video.
Key Ideas
- Pagination requires passing a page index or offset from the Presentation layer (Bloc/Cubit) all the way down to the Data layer (DataSource).
- The HomeRepo interface and its implementation are updated to accept an optional named parameter {int pageNumber = 0}.
- The HomeRemoteDataSourceImpl calculates the API's pagination offset by multiplying the requested page by the limit (pageNumber * 10).
- The FetchFeaturedBooksUseCase signature is updated to extend UseCase<List<BookEntity>, int> instead of using a NoParam type.
- The FeaturedBooksCubit is modified to accept a pageNumber parameter and forward it to the UseCase's call method.
- By providing a default value of 0 for the page number throughout the layers, the initial data fetch (e.g., triggered in main.dart) continues to work without breaking existing code.
Code Snippets
abstract class HomeRepo {
Future<Either<Failure, List<BookEntity>>> fetchFeaturedBooks({int pageNumber = 0});
Future<Either<Failure, List<BookEntity>>> fetchNewestBooks();
}abstract class HomeRemoteDataSource {
Future<List<BookEntity>> fetchFeaturedBooks({int pageNumber = 0});
Future<List<BookEntity>> fetchNewestBooks();
} @override
Future<Either<Failure, List<BookEntity>>> fetchFeaturedBooks({int pageNumber = 0}) async {
try {
var booksList = await homeRemoteDataSource.fetchFeaturedBooks(
pageNumber: pageNumber,
);
return right(booksList);
} catch (e) {
if (e is DioError) {
return left(ServerFailure.fromDioError(e));
}
return left(ServerFailure(e.toString()));
}
} @override
Future<List<BookEntity>> fetchFeaturedBooks({int pageNumber = 0}) async {
var data = await apiService.get(
endPoint: 'volumes?Filtering=free-ebooks&q=programming&startIndex=${pageNumber * 10}');
List<BookEntity> books = getBooksList(data);
saveBooksData(books, kFeaturedBox);
return books;
}class FetchFeaturedBooksUseCase extends UseCase<List<BookEntity>, int> {
final HomeRepo homeRepo;
FetchFeaturedBooksUseCase(this.homeRepo);
@override
Future<Either<Failure, List<BookEntity>>> call([int pageNumber = 0]) async {
return await homeRepo.fetchFeaturedBooks(
pageNumber: pageNumber,
);
}
} Future<void> fetchFeaturedBooks({int pageNumber = 0}) async {
emit(FeaturedBooksLoading());
var result = await featuredBooksUseCase.call(pageNumber);
result.fold((failure) {
emit(FeaturedBooksFailure(failure.errMessage));
}, (books) {
emit(FeaturedBooksSuccess(books));
});
}My Notes
- The Math Behind Pagination: The Google Books API relies on startIndex rather than a direct page number. The instructor handles this translation elegantly in the Data Source: pageNumber * 10. This maps Page 0 -> Index 0, Page 1 -> Index 10, Page 2 -> Index 20, etc.
- [Editor's note] Local Caching Implications: In home_remote_data_source_impl.dart, the method calls saveBooksData(books, kFeaturedBox). When you implement pagination alongside local caching (like Hive), you must be careful not to accidentally overwrite the entire cache box with just the contents of Page 2. A proper pagination cache strategy usually requires appending new items to the box or utilizing separate keys.
- [Editor's note] Magic Numbers: The multiplier 10 is hardcoded directly into the URL string. If the API's return limit changes, or if you decide to request 20 items per page, this will break silently. Always extract this to a const int kPageSize = 10;.