FirestoreClient is a client that helps me use a firestore interface with ease.
First of all, create Model layer inheriting FirestoreModel
protocol.
import EasyFirebaseSwift
import FirebaseFirestoreSwift
import FirebaseFirestore
// Note: FirestoreModel inherits `Codable` Protocol
struct Model: FirestoreModel {
// - MARK : Necessary
// collectionName corresponding to Firestore's CollectionName.
static var collectionName: String = "models"
// NOTE: To use PropertyWrapper such as @DocumentID, @ServerTimestamp, please import FirebaseFirestoreSwift.
@DocumentID
var ref: DocumentReference?
@ServerTimestamp
var createdAt: Timestamp?
@ServerTimestamp
var updatedAt: Timestamp?
// - MARK : Custom property
var message: String
}
To create a new document on Firestore, use create
method.
Please pay attention that All of createdAt
, updatedAt
and ref
are assigned nil
value.
let client = FirestoreClient()
// Note: new model must guarantee `createdAt`, `updatedAt` and `ref` are nil.
let model = Model(
ref: nil,
createdAt: nil,
updatedAt: nil,
message: "Test"
)
client.create(model) { reference in
model.ref = reference
} failure: { error in
print(error)
}
Using update
method, we can update an existing model.
Like create
method, we have to guarantee model's metadata (createdAt
, updatedAt
, ref
) are not nil
value.
let client = FirestoreClient()
// In this case, we fetch an exsisting data from Firestore.
client.get(uid: "example_1234567890") { model in
var model = model
model.message = "Update Test"
// Note: updated model must guarantee `createdAt`, `updatedAt` and `ref` are NOT nil.
client.update(model) { reference in
model.ref = reference
} failure: { error in
print(error)
}
} failure: { error in
print(error)
}
If we want to get snapshots once, we should use get
method, on the other hand, if we want to fetch the latest data whenever database is updated, we should use listen
method.
get
is used like the following.
Note: It is necessary to specify the type of reponse model by giving concrete type
(not existential type
) at closure parameter like the following.
client.get(
filter: [],
order: [],
limit: nil
) { (models: [Model]) in // Here, we need to specify concrete type of responsed model.
for model in models {
print(model.message)
}
} failure: { error in
print(error)
}
client.get(uid: "1234567890") { (model: Model) in
print(model.message)
} failure: { error in
print(error)
}
We can achieve both single observation and query observation of a certain collection at the same time!
listen
also specifies which model we want by giving concrete type
at closure parameter like the following.
client.listen(
filter: [],
order: [],
limit: nil
) { (models: [Model]) in // Here, we need to specify concrete type of responsed model.
for model in models {
print(model.message)
}
} failure: { error in
print(error)
}
client.listen(uid: "1234567890") { (model: Model) in
print(model.message)
} failure: { error in
print(error)
}
Supporting Combine!
if you want to check the practical usage, please see this file.
// Create
model.publisher(for: .create).sink { error in
print(error)
} receiveValue: { }
.store(in: &cancellables)
// Get
let ref = Firestore.firestore().collection("models").document("sample")
Model.publisher(for: .get(ref: ref)).sink { completion in
switch completion {
case .failure(let error):
print(error)
case .finished:
break
}
} receiveValue: { model in
print(model.message)
}
.store(in: &cancellables)
We are supporting Swift Concurrency after v1.5.0 !!
if you want to check the practical usage, please see this file.
It is possible to filter documents.
Core interface to filter documents is FirestoreQueryFilter
.
In practice, I have prepared two classes FirestoreEqualFilter
and FirestoreRangeFilter
.
Please see the following code as an example useCase for FirestoreEqualFilter
.
// Create Filter
let equalFilter = FirestoreEqualFilter(
fieldPath: "message",
value: "Update Text"
)
// Apply Filter
client.listen(
filter: [equalFilter],
order: [],
limit: nil
)
{ (models: [Model]) in
// Ensure all response pass the filtering.
let messageChecking = models.allSatisfy { model in
model.message == "Update Text"
}
// Do All models have `Update Text`? → YES
assert(messageChecking)
} failure: { error in
print(error)
}
🚧 WIP
🚧 WIP