🐕
Firestore + Codableライブラリを自作
2年ほど前からFirestore
をCodable
と一緒に使えるライブラリを自作して使っており、その自作ライブラリをSwift Package
として公開しました。
同様のライブラリ
- 公式:
FirebaseFirestoreSwift
- CodableFirebase
今回のライブラリ
特徴としては
- type-safe
- decoder/encoderをカスタムできる
- 基本的に
DocumentReference
,Query
などを拡張したもの - レスポンスは
Result
型
インストール方法
- マニュアル:ソースコードをプロジェクトに追加
- Swift Package Manager、ただし、Firebase iOS SDKがSPMでインストールされている必要があります
モデルの定義
public protocol FirestoreCodable: Codable {
var id: String { get }
}
このFirestoreCodable
に準拠したstruct
を定義することで、Firestore.Document
と変換できます。
このid
はFirestore.DocumentReference
のdocumentID
と等しくしています。
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
}
}
デモプロジェクト
参考
Discussion