프로젝트 내에서 Pagination을 사용하는 부분이 많음과 동시에 중복되는 코드를 일반화 하여 유지보수를 편하게 만들어보자. restaurant_sereen은 식당 리스트를 불러오는 함수가 있고, restaurant_detail_screen은 리뷰를 불러오는 함수가 있다. 이 두 함수 모두 Pagination을 사용하고 있고 동일한 CursorPaginationModel을 사용하고 있다. 
같은 함수를 복사하는 방법 보다 Pagination 함수를 가진 인터페이스를 만들어 공통 함수를 관리하는 방법을 학습할 수 있었다.
StateNotifier<CursorPaginationBase>를 상속하는 공통 Pagination Provider를 생성하고, 여기서 Pagination관련 함수를 작성하면 이 클래스를 상속하는 모든 클래스는 paginate() 함수를 따로 만들지 않아도 사용할 수 있다.
T extends IModelWithIdU extends IBasePaginationRepository<T>abstract class IBasePaginationRepository<T extends IModelWithId> {
Future<CursorPagination<T>> paginate({
PaginationParams? params = const PaginationParams(),
});
}
class PaginationProvider<T extends IModelWithId,
U extends IBasePaginationRepository<T>>
extends StateNotifier<CursorPaginationBase> {
final U repository;
PaginationProvider({required this.repository})
: super(CursorPaginationLoading()) {
paginate();
}
Future<void> paginate({
int fetchCount = 20,
bool fetchMore = false,
bool forceRefetch = false,
}) async {
try {
if (state is CursorPagination && !forceRefetch) {
final pState = state as CursorPagination;
if (!pState.meta.hasMore) {
return;
}
}
final isLoading = state is CursorPaginationLoading;
final isRefetching = state is CursorPaginationRefetching;
final isFetchingMore = state is CursorPaginationFetchingMore;
if (fetchMore && (isLoading || isRefetching || isFetchingMore)) {
return;
}
PaginationParams paginationParams = PaginationParams(
count: fetchCount,
);
if (fetchMore) {
// paginationParams 에서 last.id에 대해 id가 무조건 있다는 것을 알리기 위해
// CursorPagination뒤에 <T>를 추가
final pState = state as CursorPagination<T>;
state = CursorPaginationFetchingMore(
meta: pState.meta,
data: pState.data,
);
paginationParams = paginationParams.copyWith(
after: pState.data.last.id,
);
} else {
if (state is CursorPagination && !forceRefetch) {
final pState = state as CursorPagination<T>;
state = CursorPaginationRefetching<T>(
meta: pState.meta,
data: pState.data,
);
} else {
state = CursorPaginationLoading();
}
}
final resp = await repository.paginate(
params: paginationParams,
);
if (state is CursorPaginationFetchingMore) {
final pState = state as CursorPaginationFetchingMore<T>;
state = resp.copyWith(
data: [
...pState.data,
...resp.data,
],
);
} else {
state = resp;
}
} catch (e, stack) {
print(e);
print(stack);
state = CursorPaginationError(message: '데이터를 가져오지 못했습니다.');
}
}
}

paginate() 함수를 PaginationProvider에서 공통으로 관리하기 때문에 코드가 훨씬 간결해 진 것을 볼 수 있다. 추후 Paginate에 대한 로직이 바뀌더라도 부모 클래스에서 한번만 바꾸면 되기 때문에 유지보수가 편해질 것이라는 것을 기대할 수 있다.