Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/sgr-ksmt/firesnapshot
A useful Firebase-Cloud-Firestore Wrapper with Codable.
https://github.com/sgr-ksmt/firesnapshot
cloudfirestore codable decode encode firebase firestore ios model modelframework oss propertywr simple snapshot swift swift5 type-safe
Last synced: 3 months ago
JSON representation
A useful Firebase-Cloud-Firestore Wrapper with Codable.
- Host: GitHub
- URL: https://github.com/sgr-ksmt/firesnapshot
- Owner: sgr-ksmt
- License: mit
- Created: 2019-09-28T06:46:38.000Z (over 5 years ago)
- Default Branch: master
- Last Pushed: 2022-07-22T04:10:57.000Z (over 2 years ago)
- Last Synced: 2024-10-13T14:43:46.395Z (4 months ago)
- Topics: cloudfirestore, codable, decode, encode, firebase, firestore, ios, model, modelframework, oss, propertywr, simple, snapshot, swift, swift5, type-safe
- Language: Swift
- Homepage:
- Size: 224 KB
- Stars: 56
- Watchers: 4
- Forks: 9
- Open Issues: 8
-
Metadata Files:
- Readme: README.md
- Funding: .github/FUNDING.yml
- License: LICENSE
Awesome Lists containing this project
README
[![Release](https://img.shields.io/github/release/sgr-ksmt/FireSnapshot.svg?style=for-the-badge)](https://github.com/sgr-ksmt/FireSnapshot/releases)
![Swift](https://img.shields.io/badge/swift-5.1-orange.svg?style=for-the-badge)
![Firebase](https://img.shields.io/badge/firebase-6.11.0-orange.svg?style=for-the-badge)
![Platform](https://img.shields.io/badge/Platform-iOS-blue.svg?style=for-the-badge)
[![license](https://img.shields.io/github/license/sgr-ksmt/FireSnapshot.svg?style=for-the-badge)](https://github.com/sgr-ksmt/FireSnapshot/blob/master/LICENSE)**A useful Firebase-Cloud-Firestore Wrapper with Codable.**
Developed by [@sgr-ksmt](https://github.com/sgr-ksmt) [![Twitter Follow](https://img.shields.io/twitter/follow/_sgr_ksmt?label=Follow&style=social)](https://twitter.com/_sgr_ksmt)
## Table of Contents
- [Feature](#feature)
- [Usage](#usage)
- [Basic Usage](#basic-usage)
- [Advanced Usage](#advanced-usage)
- [Installation](#installation)
- [Dependencies](#dependencies)
- [Road to 1.0](#road-to-10)
- [Development](#development)
- [Communication](#communication)
- [Credit](#credit)
- [License](#license)
## Feature
- 🙌 Support Codable (Use [`FirebaseFirestoreSwift`](https://github.com/firebase/firebase-ios-sdk/tree/master/Firestore/Swift) inside).
- 🙌 Provide easy-to-use methods for CRUD, Batch, Transaction.
- 🙌 Support `array-union/array-remove`.
- 🙌 Support `FieldValue.increment`.
- 🙌 Support `FieldValue.delete()`.
- 🙌 Support KeyPath based query.### Use Swift features(version: 5.1)
- 💪 **`@propertyWrapper`**: [SE-0258](https://github.com/apple/swift-evolution/blob/master/proposals/0258-property-wrappers.md)
- 💪 **Key Path Member Lookup**: [SE-0252](https://github.com/apple/swift-evolution/blob/master/proposals/0252-keypath-dynamic-member-lookup.md)
## Usage
- 👉 [Example App](https://github.com/sgr-ksmt/FireTodo)
### Basic Usage
The type of `Document` must be conformed to the `SnapshotData` protocol.
`SnapshotData` protocol inherits `Codable`. For example:```swift
struct Product: SnapshotData {
var name: String = ""
var desc: String?
var price: Int = 0
var attributes: [String: String] = [:]
}
```It is convenient to define `DocumentPath` and `CollectionPath`.
Define path for extension of `DocumentPaths` or `CollectionPaths`.```swift
extension CollectionPaths {
static let products = CollectionPath("products")
}extension DocumentPaths {
static func product(_ productID: String) -> DocumentPath {
CollectionPaths.products.document(productID)
}
}
```Create `Snapshot` with model that comformed to `SnapshotData` and path.
```swift
let product = Snapshot(data: Product(), path: CollectionPath.products)
```In short 👇
```swift
let product = Snapshot(data: .init(), path: .products)
```You can save it by calling `create(completion:)`
```swift
product.create { error in
if let error = error {
print("error", error)
return
}
print("created!")
}
````FireSnapshot` also provides read(`get document(s)/listen document(s)`), write(`update/delete`), write with batch and transaction
```swift
// Update document
product.update { error in
if let error = error {
print("error", error)
return
}
print("updated!")
}// Delete document
product.delete { error in
if let error = error {
print("error", error)
return
}
print("deleted!")
}// Get document
Snapshot.get(.product("some_product_id")) { result in
switch result {
case let .success(product):
print(product.name)
case let .failure(error):
print(error)
}
}// Listen document
let listener = Snapshot.listen(.product("some_product_id")) { result in
switch result {
case let .success(product):
print("listened new product", product.name)
case let .failure(error):
print(error)
}
}// Get documents
Snapshot.get(.products) { result in
switch result {
case let .success(products):
print(products.count)
case let .failure(error):
print(error)
}
}// Listen documents
let listener = Snapshot.listen(.products) { result in
switch result {
case let .success(products):
print("listened new products", products.count)
case let .failure(error):
print(error)
}
}
```If you can read/write timestamp such as `createTime` and `updateTime`, model must be conform to `HasTimestamps` protocol.
```swift
struct Product: SnapshotData, HasTimestamps {
var name: String = ""
var desc: String?
var price: Int = 0
var attributes: [String: String] = [:]
}let product = Snapshot(data: .init(), path: .products)
// `createTime` and `updateTime` will be written to field with other properties.
product.create()Snapshot.get(product.path) { result in
guard let p = try? result.get() else {
return
}// optional timestamp value.
print(p.createTime)
print(p.updateTime)// `updateTime` will be updated with other properties.
p.update()
}
```
### Advanced Usage
#### `@IncrementableInt` / `@IncrementableDouble`
If you want to use `FieldValue.increment` on model, use `@IncrementableInt(Double)`.
- The type of `@IncrementableInt` property is `Int64`.
- The type of `@IncrementableDouble` property is `Double`.```swift
extension CollectionPaths {
static let products = CollectionPath("models")
}struct Model: SnapshotData {
@IncrementableInt var count = 10
@IncrementableDouble var distance = 10.0
}Snapshot.get(.model(modelID)) { result in
guard let model = try? result.get() else {
return
}
// Refer a number
print(model.count) // print `10`.
print(model.distance) // print `10.0`.// Increment (use `$` prefix)
model.$count.increment(1)
print(model.count) // print `11`.
model.update()model.$distance.increment(1.0)
print(model.distance) // print `11.0`.
model.update()// Decrement
model.$count.increment(-1)
print(model.count) // print `9`.
model.update()model.$distance.increment(-1.0)
print(model.distance) // print `9.0`.
model.update()// if you want to reset property, use `reset` method.
model.$count.reset()
}
```#### `@AtomicArray`
If you want to use `FieldValue.arrayUnion` or `FieldValue.arrayRemove`, use `@AtomicArray`.
The type of `@AtomicArray`'s element must be conformed to `Codable` protocol.
```swift
extension CollectionPaths {
static let products = CollectionPath("models")
}struct Model: SnapshotData {
@AtomicArray var languages: [String] = ["en", "ja"]
}Snapshot.get(.model(modelID)) { result in
guard let model = try? result.get() else {
return
}// Refer an array
print(model.languages) // print `["en", "ja"]`.// Union element(s)
model.$languages.union("zh")
print(model.count) // print `["en", "ja", "zh"]`.
model.update()// Remove element(s)
model.$languages.remove("en")
print(model.count) // print `["ja"]`.
model.update()// if you want to reset property, use `reset` method.
model.$languages.reset()
}
```#### `@DeletableField`
IF you want to use `FieldValue.delete`, use `@DeletableField`.
```swift
extension CollectionPaths {
static let products = CollectionPath("models")
}struct Model: SnapshotData {
var bio: DeletableField? = .init(value: "I'm a software engineer.")
}Snapshot.get(.model(modelID)) { result in
guard let model = try? result.get() else {
return
}print(model.bio?.value) // print `Optional("I'm a software engineer.")`
// Delete property
model.bio.delete()
model.update()
}// After updated
Snapshot.get(.model(modelID)) { result in
guard let model = try? result.get() else {
return
}print(model.bio) // nil
print(model.bio?.value) // nil
}
```**NOTE:**
Normally, when property is set to nil, `{key: null}` will be written to document,
but when using `FieldValue.delete`, field of `key` will be deleted from document.#### KeyPath-based query
You can use KeyPath-based query generator called `QueryBuilder` if the model conform to `FieldNameReferable` protocol.
```swift
extension CollectionPaths {
static let products = CollectionPath("products")
}struct Product: SnapshotData, HasTimestamps {
var name: String = ""
var desc: String?
var price: Int = 0
var deleted: Bool = false
var attributes: [String: String] = [:]
}extension Product: FieldNameReferable {
static var fieldNames: [PartialKeyPath : String] {
return [
\Self.self.name: "name",
\Self.self.desc: "desc",
\Self.self.price: "price",
\Self.self.deleted: "deleted",
]
}
}Snapshot.get(.products, queryBuilder: { builder in
builder
.where(\.price, isGreaterThan: 5000)
.where(\.deleted, isEqualTo: false)
.order(by: \.updateTime, descending: true)
}) { result in
...
}
```
## Installation
- CocoaPods
```ruby
pod 'FireSnapshot', '~> 0.11.1'
```
## Dependencies
- Firebase: `v6.12.0` or higher.
- FirebaseFirestoreSwift: Fetch from master branch.
- Swift: `5.1` or higher.
## Road to 1.0
- Until 1.0 is reached, minor versions will be breaking 🙇.
## Development
### Setup
```sh
$ git clone ...
$ cd path/to/FireSnapshot
$ make
$ open FireSnapshot.xcworkspace
```### Unit Test
Start `Firestore Emulator` before running Unit Test.
```sh
$ npm install -g firebase-tools
$ firebase setup:emulators:firestore
$ cd ./firebase/
$ firebase emulators:start --only firestore
# Open Xcode and run Unit Test after running emulator.
```or, run `./scripts/test.sh`.
## Communication
- If you found a bug, open an issue.
- If you have a feature request, open an issue.
- If you want to contribute, submit a pull request.:muscle:
## Credit
FireSnapshot was inspired by followings:
- [starhoshi/tart](https://github.com/starhoshi/tart)
- [alickbass/CodableFirebase](https://github.com/alickbass/CodableFirebase)
## License
**FireSnapshot** is under MIT license. See the [LICENSE](LICENSE) file for more info.