Blog
컴퓨터 공학
software-architecture
페이지 네이션

출저

본 게시글은 코드팩토리 - 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가지로 분류된다.

  1. CursorPaginationLoading - 첫 로딩 상태 (캐쉬 없음)
  2. CursorPagination - 페이지네이션이 처음 실행된 상태
  3. CursorPaginationFetchMore - 페이지네이션이 처음 실행된 이후 추가로 데이터를 불러올 때
  4. CursorPaginationError - 페이지네이션 중 에러가 발생했을 때
  5. 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});
}