Published on

Firestore listeners & Combine: ma'lumotlarni real-vaqtda (real-time) yangilash

Authors

Nega bizga Listener kerak?

Shu paytgacha biz ma'lumotlarni o'qish uchun getDocuments() dan foydalandik. Ammo bu funksiya faqat 1 marta ishlaydi. Tasavvur qiling, siz chat dasturi yaratyapsiz. Ekran ochildi, eski xabarlar yuklandi. Ammo boshqa foydalanuvchi sizga yangi xabar yuborsa, siz uni qanday ko'rasiz? Har soniyada ekranni yangilaysizmi (refresh)? Yo'q, buning o'rniga biz bazaga "Quloq tutamiz" (Listen). Qachonki bazada biron o'zgarish bo'lsa, Firebase o'zi bizga xabar beradi!

1. Oddiy Listener yaratish

Firebase'da buning uchun addSnapshotListener degan komanda mavjud. Uning ichidagi kod (closure) qachonki bazada o'zgarish bo'lsa, avtomatik tarzda takror va takror ishga tushaveradi:

func addListenerForFavorites(userId: String, completion: @escaping ([Product]) -> Void) {
    let collection = userCollection.document(userId).collection("favorite_products")

    // Qachonki baza yangilansa, shu kod qismi ishga tushadi
    collection.addSnapshotListener { snapshot, error in
        guard let documents = snapshot?.documents else { return }

        // Hujjatlarni Modelga o'girish (Decode)
        let products = documents.compactMap { try? $0.data(as: Product.self) }

        // Natijani UI'ga qaytarish
        completion(products)
    }
}

Bu kod yaxshi ishlaydi, ammo SwiftUI va zamonaviy arxitektura qoidalari (MVVM) bo'yicha bu usul biroz eskirgan va Escaping Closures bilan ishlashda "Memory Leak" (xotira to'lib qolishi) muammolariga sabab bo'lishi mumkin.

2. Combine bilan ishni Professional darajaga olib chiqish

Biz o'zimizning Generic Query extension'imizni yozamiz. U Firebase'ning Listener'ini oladi va uni Apple'ning mashhur Combine Publisher'iga aylantirib beradi! Bu bizning kodimizni fantastik darajada toza qiladi.

import FirebaseFirestore
import Combine

extension Query {
    // Endi funksiyamiz AnyPublisher va ListenerRegistration qaytaradi
    func addSnapshotListener<T: Decodable>(as type: T.Type) -> (AnyPublisher<[T], Error>, ListenerRegistration) {

        // 1. Publisher yaratamiz (U ma'lumotlarni uzoq vaqt davomida qabul qilib turadi)
        let publisher = PassthroughSubject<[T], Error>()

        // 2. Firebase Listener'ini yoqamiz
        let listener = self.addSnapshotListener { snapshot, error in
            guard let documents = snapshot?.documents else { return }

            // Hujjatlarni Modelga o'girish
            let items = documents.compactMap { try? $0.data(as: T.self) }

            // 3. Yangilangan ma'lumotni Publisher'ga "uzatamiz" (Send)
            publisher.send(items)
        }

        // 4. Ekranda faqat turini yashirgan holda (AnyPublisher) qaytaramiz
        return (publisher.eraseToAnyPublisher(), listener)
    }
}

3. UI da (View Model) qanday ishlatamiz?

Endi SwiftUI tomonida bu Publisher'ni "tinglash" (Subscribe) va natijalarni massivga joylash qoldi:

class FavoritesViewModel: ObservableObject {
    @Published var favorites: [Product] = []
    private var cancellables = Set<AnyCancellable>() // Publisher'larni saqlovchi quti

    func subscribeToFavorites(userId: String) {
        // Zanjir (Query) ni tuzamiz
        let query = Firestore.firestore().collection("users").document(userId).collection("favorite_products")

        // Bizning Sehrli Generic funksiyamiz!
        let (publisher, _) = query.addSnapshotListener(as: Product.self)

        publisher
            .sink { completion in
                // Xatolik bo'lsa ushlash
            } receiveValue: { [weak self] returnedProducts in
                // Qachon baza yangilansa, ushbu joy avtomatik ishlaydi!
                self?.favorites = returnedProducts
            }
            .store(in: &cancellables) // Xotirada to'g'ri saqlash
    }
}

TIP

Listener'larni o'chirishni unutmang: Biz funksiyamizdan ListenerRegistration obyektini ham qaytarganmiz. Foydalanuvchi Chat yoki Saralanganlar ekranidan chiqib ketganida (onDisappear da) ushbu listenerni .remove() orqali o'chirib qo'yish kerak. Aks holda, ilovangiz orqa fonda ham Firebase'ni o'qishda davom etadi va keraksiz xarajatlarga olib keladi!

Mana shunday professional arxitektura orqali ilovamiz endi Real-time (jonli) rejimda ishlashga to'liq tayyor bo'ldi.

Buy mea coffee