Giter Club home page Giter Club logo

firesnapshot's Introduction

Release Swift Firebase Platform license

A useful Firebase-Cloud-Firestore Wrapper with Codable.

Developed by @sgr-ksmt Twitter Follow


Table of Contents


Feature

  • ๐Ÿ™Œ Support Codable (Use FirebaseFirestoreSwift 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
  • ๐Ÿ’ช Key Path Member Lookup: SE-0252

Usage

Basic Usage

The type of Document must be conformed to the SnapshotData protocol.
SnapshotData protocol inherits Codable. For example:

struct Product: SnapshotData {
    var name: String = ""
    var desc: String?
    var price: Int = 0
    var attributes: [String: String] = [:]
}

It is convenient to define DocumentPath<T> and CollectionPath<T>.
Define path for extension of DocumentPaths or CollectionPaths.

extension CollectionPaths {
    static let products = CollectionPath<Product>("products")
}

extension DocumentPaths {
    static func product(_ productID: String) -> DocumentPath<Product> {
        CollectionPaths.products.document(productID)
    }
}

Create Snapshot with model that comformed to SnapshotData and path.

let product = Snapshot<Product>(data: Product(), path: CollectionPath.products)

In short ๐Ÿ‘‡

let product = Snapshot(data: .init(), path: .products)

You can save it by calling create(completion:)

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

// 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.

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.
extension CollectionPaths {
    static let products = CollectionPath<Model>("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.

extension CollectionPaths {
    static let products = CollectionPath<Model>("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.

extension CollectionPaths {
    static let products = CollectionPath<Model>("models")
}

struct Model: SnapshotData {
    var bio: DeletableField<String>? = .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.

extension CollectionPaths {
    static let products = CollectionPath<Product>("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<Mock> : 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
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

$ git clone ...
$ cd path/to/FireSnapshot
$ make
$ open FireSnapshot.xcworkspace

Unit Test

Start Firestore Emulator before running Unit Test.

$ 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:


License

FireSnapshot is under MIT license. See the LICENSE file for more info.

firesnapshot's People

Contributors

dependabot[bot] avatar dlackty avatar sgr-ksmt avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

firesnapshot's Issues

Codable extensions

Would be awesome to have extensions that return Codable publishers for the query/document observers.

WhereField filter in QueryBuilder

Using the standard Firebase library, I filtered the query to return only documents where their documentID was contained within an array using something like this:

.whereField(FirebaseFirestore.FieldPath.documentID(), in: ["123","456"])

Is it possible to replicate such a query using FireSnapshot?

Thanks

Support nullable `@Reference`.

FireSnapshot does not currently support nullable @Reference.
So, the code that like below will be failed to decode.

struct Model: SnapshotData {
    var name: String
    @Reference<User> var user = nil
}
{
  "name": "Mike",
}

(user: undefined)


But, another code that like below will be succeeded.

struct Model: SnapshotData {
    var name: String
    @Reference<User> var user = nil
}
{
  "name": "Mike",
  "user": null
}

ToDo

  • Implement NullableReference that is allowed {key: undefined}.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.