출저
본 게시글은 코드팩토리 - Flutter 중급 강의 (opens in a new tab)를 참고해서 작성되었습니다.
페이지네이션이란?
만약에 댓글창이 있다고 가정을 하고 댓글을 내려 받는다고 할 때 인기가 많은 게시물의 댓글을 내려받아서 댓글을 한 번에 5000개를 내려받게되면 이는 서버 측에도 클라이언트 측에도 부담이 되며 좋은 API가 아니다.
그래서 페이지네이션을 정의해서 한 번에 특정 개수의 데이터를 내려주게 된다.
FLUTTER
페이지네이션 Model
@JsonNaming(value = PropertyNamingStrategies.SnakeCaseStrategy.class)
public class Pagination {
private Integer page; // 몇 번째 페이지를 보여줄지
private Integer size; // 한 페이지에 보여줄 elements 수
private Integer currentElements; // 현재 return된 elements 수
private Integer totalPages; // 최대 페이지
private Long totalElements; // 전체 elements 수
}
위와 같은 Pagination을 서버측에서 정의 해놨다고 가정할 때 데이터를 받아올 Model을 정의한다.
아래와 같이 Pagination에 대한 정보를 담고 있을 meta를 작성해주고
@JsonSerializable()
class CursorPaginationMeta {
final int count;
final int size;
@JsonKey(name: 'current_elements')
final int currentElements;
@JsonKey(name: 'total_pages')
final int totalPages;
@JsonKey(name: 'total_elements')
final double totalElements;
CursorPaginationMeta(this.currentElements, this.totalPages, this.totalElements, {
required this.count,
required this.size,
});
factory CursorPaginationMeta.fromJson(Map<String, dynamic> json) => _$CursorPaginationMetaFromJson(json);
Map<String, dynamic> toJson() => _$CursorPaginationMetaToJson(this);
}
meta와 함께 들어올 데이터를 담을 model을 작성한다.
@JsonSerializable(
// 클래스가 Generic Type을 parameter로 받을 경우 해당 타입으로 직렬화함
genericArgumentFactories: true,
)
// 외부에서 generic을 지정해주기 위해 <T>를 사용
class CursorPagination<T> {
final CursorPagination meta;
final List<T> data;
CursorPagination({
required this.meta,
required this.data,
});
factory CursorPagination.fromJson(Map<String, dynamic> json, T Function(Object? json) fromJsonT) => _$CursorPaginationFromJson(json, fromJsonT);
// 여기서 의미하는 THIS는 instance를 의미
Map<String, dynamic> toJson(Object? Function(T value) toJsonT) => _$CursorPaginationToJson(this, toJsonT);
}
Repository(with Retrofit)
@RestApi()
abstract class RestaurantRepository {
// factory constuctor는 (=)을 사용해서 함수 Body를 지정해줄 수 있음
// 함수 Body를 RestaurantRepository로 지정함
factory RestaurantRepository(Dio dio, {String baseUrl/*http://$ip/restaurant*/})
= _RestaurantRepository;
// 요청 방식과 baseUrl 뒤에 붙을 path를 지정해줌
// 이 기준을 보고서 retrofit이 이 파일 안에다가 각각의 함수가 어떻게 실행되어야 하는지 정의를 하게 됨
@GET('/')
@Headers({
'accessToken' : 'true'
})
Future<CursorPagination<RestaurantModel>> paginate();
}
Pagination State Notifier
Pagination을 수행하고 view에 상태를 전달해주는 State Notifier를 작성한다.
final restaurantProvider =
StateNotifierProvider<RestaurantStateNotifier, CursorPaginationBase>((ref) {
final Dio dio = ref.read(dioProvider);
final RestaurantRepository repository =
RestaurantRepository(dio, baseUrl: 'http://$ip/restaurant');
return RestaurantStateNotifier(repo: repository);
});
class RestaurantStateNotifier extends StateNotifier<CursorPaginationBase> {
late final RestaurantRepository _repository;
RestaurantStateNotifier({required RestaurantRepository repo})
: super(CursorPaginationLoading()) {
_repository = repo;
paginate();
}
// 상세 내용 뒤에서 구현 예정
void paginate({});
}
Pagination Base 작성
Pagination의 상태에 따라서 paginate를 수행할 수 있도록 Base를 작성한다.
Base를 작성하는 상태 값은 크게 다음과 같이 5가지로 분류된다.
- CursorPaginationLoading - 첫 로딩 상태 (캐쉬 없음)
- CursorPagination - 페이지네이션이 처음 실행된 상태
- CursorPaginationFetchMore - 페이지네이션이 처음 실행된 이후 추가로 데이터를 불러올 때
- CursorPaginationError - 페이지네이션 중 에러가 발생했을 때
- CursorPaginationForceRefetching - 페이지네이션을 초기 Loading 상태로 만든 후 처음부터 다시 시작할 때
abstract class CursorPaginationBase {}
class CursorPaginationError extends CursorPaginationBase {
final String errMsg;
CursorPaginationError({required this.errMsg});
}
class CursorPaginationLoading extends CursorPaginationBase {}
@JsonSerializable(
// 클래스가 Generic Type을 parameter로 받을 경우 해당 타입으로 직렬화함
genericArgumentFactories: true,
)
// 외부에서 generic을 지정해주기 위해 <T>를 사용
class CursorPagination<T> extends CursorPaginationBase {
final CursorPaginationMeta meta;
final List<T> data;
CursorPagination({
required this.meta,
required this.data,
});
factory CursorPagination.fromJson(
Map<String, dynamic> json, T Function(Object? json) fromJsonT) =>
_$CursorPaginationFromJson(json, fromJsonT);
// 여기서 의미하는 this는 instance를 의미
Map<String, dynamic> toJson(Object? Function(T value) toJsonT) =>
_$CursorPaginationToJson(this, toJsonT);
}
// 새로 Fetching 하는 경우이기 때문에 CursorPagination을 상속해야 함
// 이미 meta와 data가 있기 때문에 그렇다
class CursorPaginationRefetching<T>extends CursorPagination<T> {
CursorPaginationRefetching({required super.meta, required super.data});
}
// List의 맨 마지막으로 내려서 추가 데이터를 요청하는 중인 경우
class CursorPaginationFetchingMore<T> extends CursorPagination<T> {
CursorPaginationFetchingMore({required super.meta, required super.data});
}