0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Swift FireBase FireStoreのrx実装

Last updated at Posted at 2025-09-22

FireStoreからRxSwiftで値を参照

こんなふうにしたいために

    private static let database = Firestore.firestore()
    
    static func getDetails(prefecture_id: Int) -> Observable<[Kouyou]> {
        return database.collection("/kouyou")
            .whereField("prefecture_id", isEqualTo: prefecture_id)
            .rx.getDocuments()                  // QuerySnapshot
            .map { $0.documents }               // [QueryDocumentSnapshot]
            .map { $0.compactMap{ doc in return try? Kouyou.init(from: doc) } }
        
    }

RxFirebase/Firestore というのがあったはずだけど最新の環境では動かなかったので同じように使えるものを考えてみた。

設計

Firestore の「1回だけ getDocuments する非同期処理」を、RxSwift の Observable として扱えるようにラップ

1 拡張対象

extension Reactive where Base: Query {
  • Reactive は RxSwift が提供するラッパー
  • where Base: Query なので Firestore の Query を拡張
  • これにより query.rx.getDocuments() のように使える

2 関数定義

func getDocuments() -> Observable<QuerySnapshot>
  • Firestore の getDocuments を呼び出して、その結果を RxSwift の Observable に変換する関数
  • 戻り値は Observable(Firestore の結果を流すストリーム)

3 Observable の生成

return Observable.create { observer in
    self.base.getDocuments { snapshot, error in
  • Observable.create で カスタムな Observable を作成
  • self.base は拡張元の Query インスタンス(Firestore のクエリ)
  • Firestore の非同期 API(クロージャで結果が返る)をRx の仕組みに変換

4 Firestore の結果を Observer に流す

if let error = error {
    observer.onError(error)   // エラーがあればストリームに流す
} else if let snapshot = snapshot {
    observer.onNext(snapshot) // データを流す
    observer.onCompleted()    // 1回きりで終了
}
  • 成功時 → onNext(snapshot) で結果を流し、onCompleted() で終了
  • 無限に流れ続けるストリームではなく「一度だけ」
  • 失敗時 → onError(error) でストリームをエラー終了

5 リソース解放

return Disposables.create()
  • Observable の購読が dispose されたときのクリーンアップ処理
  • 今回は特別な解放処理が不要なので空の Disposables.create() を返却

作成したコード

extension Reactive where Base: Query {
    /// Firestore の getDocuments を 1回だけ Observable で流す
    func getDocuments() -> Observable<QuerySnapshot> {
        return Observable.create { observer in
            self.base.getDocuments { snapshot, error in    // 元の Query インスタンスを self.base として参照
                if let error = error {
                    observer.onError(error)
                } else if let snapshot = snapshot {
                    observer.onNext(snapshot)
                    observer.onCompleted()   // ここで終了
                }
            }
            return Disposables.create()
        }
    }
}

利用例

Firestore.firestore()
    .collection("/kouyou")
    .whereField("prefecture_id", isEqualTo: prefecture_id) // Query を作成
    .rx.getDocuments()                                     // 拡張した Observable 化メソッド
    .map { $0.documents }                                  // QuerySnapshot を [DocumentSnapshot] に変換
    .subscribe(onNext: { docs in                          // 取得した配列を受け取る
        print(docs)                 // 成功してドキュメントが来たとき
    },
    onError: { error in
        print("エラー発生: \(error)") // Firestore 側で失敗したとき
    },
    onCompleted: {
        print("読み取り完了")         // 正常に完了したとき
    })
    .disposed(by: disposeBag)                              // 購読のライフサイクル管理

またはこんなのを作って

import RxSwift
import RxCocoa

enum Result<Response> {
    case succeeded(Response)
    case failed(Error)
}

extension ObservableConvertibleType {
    func resultDriver() -> Driver<Result<Element>> {
        return self.asObservable()
            .map { Result.succeeded($0) }
            .asDriver { Driver.just(Result.failed($0)) }
    }

    func materializeAsDriver() -> Driver<Event<Element>> {
        return self.asObservable()
            .materialize()
            .asDriver(onErrorDriveWith: .empty())
    }
}

extension SharedSequence {
    /// split result to Element and Error
    ///
    /// - Parameter result: Driver<Result<Element>>
    /// - Returns: Driver<Element>, Driver<Error>
    static func split(result: Driver<Result<Element>>) -> (response: Driver<Element>, error: Driver<Error>) {
        let responseDriver = result.flatMap { result -> Driver<Element> in
            switch result {
            case .succeeded(let response):
                return Driver.just(response)
            case .failed:
                return Driver.empty()
            } }
        let errorDriver = result.flatMap { result -> Driver<Error> in
            switch result {
            case .succeeded:
                return Driver.empty()
            case .failed(let error):
                return Driver.just(error)
            } }
        return (responseDriver, errorDriver)
    }
}

こうする

import RxSwift
import RxCocoa
import Firebase
import FirebaseFirestore

class KouyouService {

    private static let database = Firestore.firestore()
    
    static func getDetails(prefecture_id: Int) -> Observable<[Kouyou]> {
        return database.collection("/kouyou")
            .whereField("prefecture_id", isEqualTo: prefecture_id)
            .rx.getDocuments()                  // QuerySnapshot
            .map { $0.documents }               // [QueryDocumentSnapshot]
            .map { $0.compactMap{ doc in return try? Kouyou.init(from: doc) } }
        
    }
}
    // FireStoreから最新データを取得
    let (list, error) = Driver.split(result: KouyouService.getDetails(prefecture_id: self.prefecure_id).resultDriver())

    list.asDriver().drive(onNext: {[unowned self] details in
        let data = details.compactMap { self.convertDetail($0) }
    }).disposed(by: rx.disposeBag)
    
    
    error.asDriver().drive(onNext: { error in
        print(error)
    }).disposed(by: rx.disposeBag)

cf. https://qiita.com/katafuchix/items/1bd2c2f7590b60a104a7

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?