[SwiftUI Firebase] Pagination, Limits and Aggregations

Woozoo·2023년 4월 11일

[SwiftUI Firebase]

목록 보기
11/14

pagination은 페이징 같은 거라 생각하면 됨

지금 ProductManager를 보면 getAllProducts가
전체 Document를 가지고 오고 있음

그럼 어떻게 하느냐
심플함!


.limit으로 몇개 가져올 지 정해주면 됨

근데 이거 진짜 몇개 가져올 지 딱 끊는 거라
5개 적으면 5개 이후로는 안 가져옴

sorting하는 것도 마찬가지로 limit 걸어줄 수 있음


toLast는 마지막 3개 가져오는 거

실제 앱이었으면 limit을 파라미터로 받아서 inject해줄 수 있겠죠



rating 기준으로 sorting한 다음에 limit 걸어줌

뷰모델에 메소드 작성해주고 Task로 getProductsByRating 호출해줬다

딱 원하는 만큼만 가져오는 걸 볼 수 있음!

근데 버튼 누를 때마다 다음 것들도 추가되게 하고 싶다면 ?

이렇게하면 똑같은것만 계속 추가하잖음

어떻게 해야할까?

요런 메소드가 있긴한데 이건 시작점의 값을 주는 거임



start(after:)를 사용해서 해볼 수 있음

lastRating을 products array의 마지막 아이템의 rating값으로 넣어주는겨!

근데 lastRating의 값이 같은 아이템이 있으면 어떡함?
요럴 땐 사용하면 안됨!


고럼 어떻게 해야할까?

마지막 Document를 가지고 체크를 하는거임!

func getProductsByRating(count: Int, lastDocument: DocumentSnapshot?) async throws -> [Product] {
    if let lastDocument {
        return try await productsCollection
            .order(by: Product.CodingKeys.rating.rawValue, descending: true)
            .limit(to: count)
            .start(afterDocument: lastDocument)
            .getDocuments(as: Product.self)
    } else {
        return try await productsCollection
            .order(by: Product.CodingKeys.rating.rawValue, descending: true)
            .limit(to: count)
            .getDocuments(as: Product.self)
    }
}


getDocuments 수정해주는데

func getDocuments<T>(as type: T.Type) async throws -> [T] where T : Decodable {
    try await getDocumentsWithSnapshot(as: type).products
}

func getDocumentsWithSnapshot<T>(as type: T.Type) async throws -> (products: [T], lastDocument: DocumentSnapshot?) where T : Decodable {
    let snapshot = try await self.getDocuments()
    
    let products = try snapshot.documents.map({ document in
        try document.data(as: T.self)
    })
    return (products, snapshot.documents.last)
}

요렇게까지 수정 가능


그래서 이 메소드를 가지고 Product랑 document 둘다 return하게끔 해주는 거


뷰모델에서 lastDocument 변수로 선언하고 이 값을 받아오게 해줌

private var lastDocument: DocumentSnapshot? = nil해준거 쓰려면 import FirebaseFirestore 해줘야하는데 매번 할 필요 없음! struct같은 구조체에 다 모다놓으면 그 struct만 가져와서 쓰면 되니까 이런 식으로 해줄 수도 있습니다~


제일 기본적인 pagination 을 구현해봅시다


ProductsManager로 돌아와서 private으로 선언한 애들은 전부 .getDocuments(as:) 메소드 쓰고 있는 걸 볼 수 있음

이거 고쳐봅시다

private func getAllProductsQuery() -> Query {
    productsCollection
}

private func getAllProductsSortedByPriceQuery(descending: Bool) -> Query {
    productsCollection
        .order(by: Product.CodingKeys.price.rawValue, descending: descending)
}

private func getAllProductsCategoryQuery(category: String) -> Query {
    productsCollection
        .whereField(Product.CodingKeys.category.rawValue, isEqualTo: category)
}

private func getAllProductsByPriceAndCategoryQuery(descending: Bool, category: String) -> Query {
    productsCollection
        .whereField(Product.CodingKeys.category.rawValue, isEqualTo: category)
        .order(by: Product.CodingKeys.price.rawValue, descending: descending)
}

func getAllProducts(priceDescending descending: Bool?, forCategory category: String?) async throws -> [Product] {
    
    var query: Query = getAllProductsQuery()
    
    if let descending, let category {
        query = getAllProductsByPriceAndCategoryQuery(descending: descending, category: category)
    } else if let descending {
        query = getAllProductsSortedByPriceQuery(descending: descending)
    } else if let category {
        query = getAllProductsCategoryQuery(category: category)
    }
    
    return try await query
        .getDocuments(as: Product.self)
}

ProductsManager에서 Query로 메소드를 변경해주고
해당 query들을 if 조건문으로 갈래가 나눠지게 해줌!


그리고 getAllProducts 메소드에서 .limit을 붙여주면 아까 갈래길에 전부다 .limit 해주지 않아도 되잖음!
조금 더 코드가 깔끔해졌음


오케이 여기까지 하고 10개만 가져오게 했는데
스크롤 내리다보면 자동으로 새로운 Batch를 가져오게 하고 싶잖음
아까 했던 lastDocument 방법을 이용해봅시다

func getAllProducts(priceDescending descending: Bool?, forCategory category: String?, count: Int, lastDocument: DocumentSnapshot?) async throws -> (products: [Product], lastDocument: DocumentSnapshot?) {
    
    var query: Query = getAllProductsQuery()
    
    if let descending, let category {
        query = getAllProductsByPriceAndCategoryQuery(descending: descending, category: category)
    } else if let descending {
        query = getAllProductsSortedByPriceQuery(descending: descending)
    } else if let category {
        query = getAllProductsCategoryQuery(category: category)
    }
    
    if let lastDocument {
        return try await query
            .limit(to: count)
            .start(afterDocument: lastDocument)
            .getDocumentsWithSnapshot(as: Product.self)
    } else {
        return try await query
            .limit(to: count)
            .getDocumentsWithSnapshot(as: Product.self)
    }
}

튜플로 Product array랑 DocumentSnapshot 반환하게 만들어주고 lastDocument가 옵셔널 하니까 if let 으로 옵셔널 해제!

아까 Query의 extension으로 구현해줬던 메소드도

func getDocumentsWithSnapshot<T>(as type: T.Type) async throws -> (products: [T], lastDocument: DocumentSnapshot?) where T : Decodable
가지고 와줬습니다


뷰 수정해줍시다

product 루프하면서 마지막 아이템이라면 ProgressView()를 보여주게 끔 할라는데
이렇게 구현하려면 product가 equatable을 준수해야함!
그래서 equatable 채택해줘도 되고 아니면 id가지고 비교 해도 됩니다~


요런 식으로 마지막 products라면 ProgressView()가 뜨게 하고
이게 onAppear일 때 다시 get 메소드 호출해주면 됨!


근데 또 filter걸면 다 살아 있음


리셋 해주면 됩니다~


lastDocument nil인지 체크도 해줘야함


Query extension 추가해서 더 깔끔하게 만들어봅시다


하나 더!
가져오는 데이터가 몇개인지 어떻게 파악할 수 있을까?

요메소드 사용하면 됨



profile
우주형

0개의 댓글