🐕

Firestore + Codableライブラリを自作

2021/07/05に公開

2年ほど前からFirestoreCodableと一緒に使えるライブラリを自作して使っており、その自作ライブラリをSwift Packageとして公開しました。

同様のライブラリ

今回のライブラリ

https://github.com/usk2000/FirebaseCodable

特徴としては

  • type-safe
  • decoder/encoderをカスタムできる
  • 基本的にDocumentReference, Queryなどを拡張したもの
  • レスポンスはResult

インストール方法

  1. マニュアル:ソースコードをプロジェクトに追加
  2. Swift Package Manager、ただし、Firebase iOS SDKがSPMでインストールされている必要があります

モデルの定義

public protocol FirestoreCodable: Codable {
    var id: String { get }
}

このFirestoreCodableに準拠したstructを定義することで、Firestore.Documentと変換できます。
このidFirestore.DocumentReferencedocumentIDと等しくしています。

struct SampleModel: FirestoreCodable, Equatable {
    var id: String
    let title: String
    let date: Date
}

使い方

単一ドキュメント

Get

let ref = firestore.collection("samples").document("single_document")

ref.getDocumentAs(SampleModel.self, decoder: FCDefaultJSONDecoder()) { result in
    switch result {
    case .success(let response):
        //success
    case .failure(let error):
        //error
    }
}

Set

let ref = firestore.collection("samples").document()

let model = SampleModel.init(id: "tmp", title: "sample text", date: Date())

ref.setDataAs(model, encoder: FCDeafaultJSONEncoder()) { result in
    switch result {
    case .success:
        //success        
    case .failure(let error):
        //error
    }
}

ここでid = tmpとしていますが、setする時はidは無視しているためです。

Update

let ref = firestore.collection("samples").document("single_document")
ref.updateData(fields: ["date": Date()]) { result in
    switch result {
    case .success:
        //success        
    case .failure(let error):
        //error
    }
}

Delete

let ref = firestore.collection("samples").document("single_document")
ref.delete { result in
    switch result {
    case .success:
        //success        
    case .failure(let error):
        //error
    }
}

Observe update

変更の監視を行います。

let ref = firestore.collection("samples").document("single_document")
listener = ref.addUpdateListenerAs(SampleModel.self, decoder: FCDefaultJSONDecoder()) { result in
    switch result {
    case .success(let response):
        //updated        
    case .failure(let error):
        //error
    }
}

複数ドキュメント

Get

let query = firestore.collection("samples")
    .order(by: "date", descending: true)
    .limit(to: 10)

query.getDocumentResponseAs(SampleModel.self, source: .default, decoder: FCDefaultJSONDecoder()) { [weak self] result in
    switch result {
    case .success(let response):        
        //response.items: [SampleModel]
        //response.lastSnapshot: DocumentSnapshot, which used for cursor        
    case .failure(let error):
        //error
    }
}

Observe update

変更の監視

//observe only first document for this sample
let query = firestore.collection("samples")
    .order(by: "date", descending: true)
    .limit(to: 1)

listener = query.addSnapshotListenerAs(SampleModel.self, decoder: FCDefaultJSONDecoder()) { result in
    switch result {
    case .success(let snapshotDiff):
        
        debugPrint("added    : \(snapshotDiff.added.count)")
        debugPrint("modified : \(snapshotDiff.modified.count)")
        debugPrint("removed  : \(snapshotDiff.removed.count)")
        
        //apply changes
        FCSnapshotDiff.apply(diffs: snapshotDiff, value: &samples)
        
    case .failure(let error):
        debugPrint(error)
    }
}

カスタムDecoder/Encoder

FCJsonDecoderProtocol/FCJsonEncoderProtocolを継承したJSONDecoder/JSONEncoderサブクラスを作成します。

class CustomDecoder: JSONDecoder, FCJsonDecoderProtocol {
    
    override init() {
        super.init()
        self.keyDecodingStrategy = .convertFromSnakeCase
        self.dateDecodingStrategy = .millisecondsSince1970
    }
    
}

デモプロジェクト

https://github.com/usk2000/FirebaseCodableDemo

参考

https://qiita.com/fromkk/items/33357fc6aa90e1098b48

Discussion