Section12 Pagination ListView 일반화

sihyun·2024년 4월 1일

Pagination view Component 일반화

Pagination을 사용하는 Screen에서는 가져온 데이터를 "DataName"Card라는 StatelessWidget 클래스를 통해 반복적으로 UI를 생성하고 있다.

Pagination Screen 공통 기능

  • ScrollController에 listener를 등록해서 목록이 끝나갈때 쯤 paginate 실행
  • 로딩, 에러 시 해당 UI 출력

Data에 해당하는 Card를 자동으로 생성하는 것은 파라미터를 통해 넘겨줄 수 있으나 그에 따른 행동(페이지 이동)나, 추가 행동에 관하여 모든 것을 일반화 할 순 없기 때문에 Itambuilder를 만들었다. 즉 추가 행동은 각각의 스크린에 맞게 개발할 수 있다.

typedef PaginationWidgetBuilder<T extends IModelWithId> = Widget Function(
    BuildContext, int index, T model);
    

class PaginationListView<T extends IModelWithId>
    extends ConsumerStatefulWidget {
  final StateNotifierProvider<PaginationProvider, CursorPaginationBase>
      provider;
  final PaginationWidgetBuilder<T> itemBuilder;

  const PaginationListView({
    super.key,
    required this.provider,
    required this.itemBuilder,
  });

  
  ConsumerState<PaginationListView> createState() =>
      _PaginationListViewState<T>();
}

class _PaginationListViewState<T extends IModelWithId>
    extends ConsumerState<PaginationListView> {
  final ScrollController controller = ScrollController();

  
  void initState() {
    super.initState();

    controller.addListener(listener);
  }

  void listener() {
    PaginationUtils.paginate(
      controller: controller,
      provider: ref.read(widget.provider.notifier),
    );
  }

  
  void dispose() {
    controller.removeListener(listener);
    controller.dispose();

    super.dispose();
  }

  
  Widget build(BuildContext context) {
    final state = ref.watch(widget.provider);

    if (state is CursorPaginationLoading) {
      // 로딩 UI
    }

    if (state is CursorPaginationError) {
      // Error UI
    }

    final cp = state as CursorPagination<T>;

    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 16.0),
      child: ListView.separated(
        controller: controller,
        itemCount: cp.data.length + 1,
        itemBuilder: (_, index) {
          if (index == cp.data.length) {
            // 추가 Paginate용 로딩 UI
          }
          final pItem = cp.data[index];
			
          // 호출 시 받았던 itemBuilder에서 받은 return 함수에 들어갈 파라미터 추가
          return widget.itemBuilder(
            context,
            index,
            pItem,
          );
        },
        separatorBuilder: (_, index) {
          return const SizedBox(height: 16);
        },
      ),
    );
  }
}

활용


class ProductScreen extends StatelessWidget {
  const ProductScreen({super.key});

  
  Widget build(BuildContext context) {
    return PaginationListView<ProductModel>(
      provider: productProvider,
      itemBuilder: <ProductModel>(context, index, model) {
        return GestureDetector(
          onTap: () {
            Navigator.of(context).push(MaterialPageRoute(
              builder: (_) => RestaurantDetailScreen(id: model.restaurant.id),
            ));
          },
          child: ProductCard.fromProductModel(
            model: model,
          ),
        );
      },
    );
  }
}

이렇게 세부 행동에 대해서는 itembuilder를 직접 정의함으로써 커스텀이 가능하도록 유지하고, 공통 UI에 대해서는 일반화 할 수 있는 코드를 작성할 수 있도록 일반화 하는 버릇을 들여야 할 것 같다.

profile
주니어 Flutter 개발자

0개의 댓글