Section11 Pagination 일반화

sihyun·2024년 4월 1일

Pagination 일반화

프로젝트 내에서 Pagination을 사용하는 부분이 많음과 동시에 중복되는 코드를 일반화 하여 유지보수를 편하게 만들어보자. restaurant_sereen은 식당 리스트를 불러오는 함수가 있고, restaurant_detail_screen은 리뷰를 불러오는 함수가 있다. 이 두 함수 모두 Pagination을 사용하고 있고 동일한 CursorPaginationModel을 사용하고 있다.
같은 함수를 복사하는 방법 보다 Pagination 함수를 가진 인터페이스를 만들어 공통 함수를 관리하는 방법을 학습할 수 있었다.

Pagination Provider

StateNotifier<CursorPaginationBase>를 상속하는 공통 Pagination Provider를 생성하고, 여기서 Pagination관련 함수를 작성하면 이 클래스를 상속하는 모든 클래스는 paginate() 함수를 따로 만들지 않아도 사용할 수 있다.

준비

  • T extends IModelWithId
    • Pagination의 결과로 CursorPagination의 Data의 타입을 IModelWithId를 상속한 클래스를 넣어준다.
  • U extends IBasePaginationRepository<T>
    • Pagination을 하기 위한 Repository에 대한 정보를 넣기 위해 IBasePaginationRepository를 상속한 클래스를 넣어준다.
    • PaginationProvider에서 repository에 접근해 paginate 함수를 실행해야 하기 때문에 IBasePaginationRepository에는 paginate 함수가 들어 있다.
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에 대한 로직이 바뀌더라도 부모 클래스에서 한번만 바꾸면 되기 때문에 유지보수가 편해질 것이라는 것을 기대할 수 있다.

profile
주니어 Flutter 개발자

0개의 댓글