[개발하는남자 핸즈온 플러터] 챕터17 홈 화면 상품 리스트 리뷰
개요
안녕하세요, 개발하는남자 개남입니다. 지난 포스트에 이어서 챕터 17 홈 화면 상품 리스트 페이지에대해 리뷰하겠습니다. 이번 챕터에서는 최신화작업은 할 필요는 없습니다. 이번 챕터에서는 파이어베이스의 페이지네이션 관련내용과 이미지 캐시위젯을 사용한 개선 작업에 대한 내용을 다루려고 합니다.
소스코드 : https://github.com/sudar-life/bamtol_market_clone_coding
브런치 정보 : chapter17 플러터 버전 : Flutter 3.32.5
Dart 버전 : Dart 3.8.1
1.홈 화면 데이터 로드 (페이지네이션)
책에 설명으로 getProducts 함수를 통해 firebase database로 부터 데이터를 로드하는 방법에 대해서 다음과 같이 소스코드로 설명하고 있습니다.
Future<({List<Product> list, QueryDocumentSnapshot<Object?>? lastItem})> getProducts(ProductSearchOption searchOption) async { try { Query<Object?> query = searchOption.toQuery(products); QuerySnapshot<Object?> snapshot; if (searchOption.lastItem == null) { snapshot = await query.limit(7).get(); } else { snapshot = await query .startAfterDocument(searchOption.lastItem!) .limit(7) .get(); } if (snapshot.docs.isNotEmpty) { return ( list: snapshot.docs.map<Product>((product) { return Product.fromJson( product.id, product.data() as Map<String, dynamic>); }).toList(), lastItem: snapshot.docs.last ); } return (list: <Product>[], lastItem: null); } catch (e) { print(e); return (list: <Product>[], lastItem: null); } }
여기서 주목해봐야 하는 소스코드는
if (searchOption.lastItem == null) { snapshot = await query.limit(7).get(); } else { snapshot = await query .startAfterDocument(searchOption.lastItem!) .limit(7) .get(); }
이부분과
return ( list: snapshot.docs.map<Product>((product) { return Product.fromJson( product.id, product.data() as Map<String, dynamic>); }).toList(), lastItem: snapshot.docs.last );
이부분 입니다.
lastItem이 있다면 startAfterDocument를 사용하고 있는데 함수명만 보더라도 해당 함수에 들어가는 데이터 이후로 7개의 데이터를 load 하겠다는 뜻으로 해석할 수 있습니다.
그리고 아래 부분에 작성된 return 데이터로 lastItem을 별도로 넘겨 주고 있는 것을 볼 수 있습니다.
이걸 통해서 파이어베이스를 통해 페이지네이션을 할 수 있음을 알 수 있습니다.
그럼 우선 페이지네이션을 왜 사용하는 걸까요? 그냥 있는 정보 다 보여주면 개발도 편하고 좋을텐데 왜? 페이지네이션으로 데이터를 불러와야하는지 먼저 간단하게 살펴보도록 하겠습니다.
페이징(Pagination)이란? 왜 필요할까?
페이징은 대량의 데이터를 한 번에 모두 불러오지 않고, '페이지' 단위로 나누어 불러오는 기법입니다.
1. 성능 향상:
수만, 수백만 개의 데이터를 한 번에 불러온다면 앱은 엄청난 메모리를 사용하게 되고, 네트워크 비용도 급증하며, 결국 멈추거나 느려지게 됩니다. 페이징은 딱 필요한 만큼의 데이터만 요청하여 로딩 속도를 비약적으로 향상시킵니다.
2. 사용자 경험(UX) 개선:
사용자는 모든 데이터를 한 번에 보길 원하지 않습니다. 스크롤을 내릴 때마다 자연스럽게 새로운 콘텐츠가 로딩되는 '무한 스크롤' 경험은 페이징 덕분에 가능합니다.
3. 서버 부하 감소:
클라이언트가 작은 단위로 데이터를 요청하므로 서버와 데이터베이스의 부하를 줄일 수 있습니다.
이러한 이유로 많은 양의 데이터를 불러올때 페이징 처리는 필수라 할 수 있습니다.
일반적으로 개발을 조금이라도 경험하신 분들이라면 페이징처리로 offset 기반의 페이징을 경험해 봤을것입니다. 그럼 offset 기반의 페이징과 firebase의 커서 기반의 페이징의 장단을 살펴 보도록 하겠습니다.
페이징 비교(offset/corsur)
전통적인 데이터베이스의 페이징: Offset 기반 페이징
전통적인 SQL 기반 데이터베이스(MySQL, PostgreSQL 등)는 주로 Offset 기반 페이징을 사용합니다. OFFSET과 LIMIT (또는 SKIP과 TAKE) 키워드를 이용하는 방식입니다.
동작 방식
LIMIT: 한 페이지에 가져올 데이터의 개수 (페이지 크기)OFFSET: 건너뛸 데이터의 개수
예를 들어, 한 페이지에 10개의 게시물을 보여주고 싶다면:
- 1페이지:
SELECT * FROM posts ORDER BY createdAt DESC LIMIT 10 OFFSET 0;(0개를 건너뛰고 10개 가져오기) - 2페이지:
SELECT * FROM posts ORDER BY createdAt DESC LIMIT 10 OFFSET 10;(10개를 건너뛰고 10개 가져오기) - N페이지:
SELECT * FROM posts ORDER BY createdAt DESC LIMIT 10 OFFSET (N-1)*10;
장점
- 구현이 직관적: 페이지 번호만 알면 원하는 페이지의 데이터를 바로 요청할 수 있어 매우 간단합니다.
- 자유로운 페이지 이동: "5페이지로 바로가기", "마지막 페이지로 가기" 등의 기능을 쉽게 구현할 수 있습니다.
단점
- 성능 저하: 가장 치명적인 단점입니다. 페이지 번호가 뒤로 갈수록(
OFFSET값이 커질수록) 쿼리 속도가 급격히 느려집니다. 데이터베이스는OFFSET에 지정된 수만큼의 데이터를 일단 다 읽고 버린 후에야LIMIT만큼의 데이터를 가져오기 때문입니다.OFFSET 1000000이라면, 100만 개의 데이터를 스캔하고 버리는 비효율이 발생합니다. - 데이터 누락/중복 발생: 데이터가 실시간으로 추가되거나 삭제될 때 문제가 발생합니다.
- 예시: 1페이지를 보고 있는데, 새로운 게시물 1개가 추가되었다고 가정해봅시다. 이제 2페이지를 요청하면, 원래 1페이지의 마지막 항목이었던 데이터가 2페이지의 첫 번째 항목으로 다시 나타나는 중복 현상이 발생할 수 있습니다. 반대로 데이터가 삭제되면 특정 항목을 건너뛰는 누락 현상이 발생할 수 있습니다.
Firebase (Firestore)의 페이징: 커서 기반 페이징
Firebase Firestore는 NoSQL 데이터베이스로, 전통적인 OFFSET을 지원하지 않습니다. 대신 커서(Cursor) 기반 페이징 방식을 사용합니다.
동작 방식
커서 기반 페이징은 "N개를 건너뛰고 가져와줘"가 아니라, "이 특정 문서(document) 다음부터 M개를 가져와줘" 라고 요청하는 방식입니다. 여기서 '특정 문서'의 정보가 바로 커서 역할을 합니다.
Firestore SDK에서는 startAfter() 또는 startAt() 같은 메서드를 사용합니다. 마지막으로 불러온 데이터의 DocumentSnapshot을 다음 쿼리의 시작점으로 지정하는 것입니다.
- 첫 페이지:
db.collection('posts').orderBy('createdAt', descending: true).limit(10).get() - 다음 페이지: 첫 페이지 요청에서 마지막으로 받은 문서(
lastVisible)를 이용하여,db.collection('posts').orderBy('createdAt', descending: true).startAfter(lastVisible).limit(10).get()
장점
- 일관된 성능: 페이지가 아무리 뒤로 가더라도 쿼리 성능이 거의 일정하게 유지됩니다. 데이터베이스는
OFFSET처럼 불필요한 데이터를 스캔할 필요 없이, 커서로 지정된 위치에서 바로 스캔을 시작하면 되기 때문입니다. 이는 대용량 데이터셋에서 엄청난 이점입니다. - 데이터 정합성: 실시간으로 데이터가 추가되거나 삭제되어도 데이터의 누락이나 중복이 발생하지 않습니다. 기준점이 상대적인 '개수'가 아닌, 고유한 '문서'이기 때문입니다. 새로운 데이터가 추가되어도 내 "마지막으로 본 문서"의 위치는 변하지 않습니다.
단점
- 자유로운 페이지 이동 불가: "5페이지로 바로가기"와 같은 기능을 구현하기가 매우 어렵습니다. 오직 '다음 페이지' 또는 '이전 페이지'로만 이동할 수 있습니다.
- 상대적으로 복잡한 상태 관리: 클라이언트는 다음 페이지를 요청하기 위해 항상 '마지막으로 본 문서'의 정보(
DocumentSnapshot)를 상태로 저장하고 관리해야 합니다.
두개의 페이징 기법에는 장점/단점 모두 존재합니다. 파이어베이스의 경우 커서 기반의 페이징을 지원하고 있기 때문에 위 소스코드 처럼 lastItem을 별도로 관리하여 해당 포인트(corsor) 기점에서 7개의 데이터를 로드하게 되는 것입니다.
2.이미지 캐시위젯
리스트
Write by :
개발하는남자
Developer
💬댓글 (0)
댓글을 작성하려면 로그인이 필요합니다.
댓글을 불러오는 중...