• Stars
    star
    259
  • Rank 157,669 (Top 4 %)
  • Language
    Swift
  • License
    MIT License
  • Created about 7 years ago
  • Updated over 5 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Cloud Firestore model framework for iOS - Google

โš ๏ธ Pring is deprecated

Please use ๐Ÿงข Ballcap ๐Ÿงข

Ballcap is new Cloud Firestore framework


Version Platform Downloads

Please donate to continue development.

https://github.com/1amageek/pring.ts

Pring <ฮฒ>

Firestore model framework. The concept of Document and Collection has been added to Firestore. Pring defines the Scheme of the Document and enables type - safe programming. SubCollection can also be defined in Scheme.

Deep Dive into the Firebase

Please report issues here

Requirements โ—๏ธ

Installation โš™

CocoaPods

  • Insert pod 'Pring' to your Podfile.
  • Run pod install.

Feature ๐ŸŽŠ

โ˜‘๏ธ You can define Firestore's Document scheme.
โ˜‘๏ธ Of course type safety.
โ˜‘๏ธ It seamlessly works with Firestore and Storage.
โ˜‘๏ธ You can easily associate subcollections.
โ˜‘๏ธ Support GeoPoint.

Design ๐Ÿ’ป

Firestore Database Design

If you are going to use Firestore and make products, I recommend you to read it.

TODO โœ…

Implementation

  • Implement DataType that Firestore can handle
  • Implement data management
  • Implement custom DataType (Specification under consideration)
  • Implement linkage with Firestorage
  • Implement the NestedCollection feature
  • Implement the ReferenceCollection feature
  • Implement DataSource
  • Implement Query-enabled DataSource (Specification under consideration)

Verification (Running Unit test)

  • Verify the implementation of DataType that Firestore can handle
  • Verify the implementation of data management
  • Verify the implementation of custom DataType
  • Verify cooperation with Firestorage
  • Verify the implementation of the NestedCollection feature
  • Verify the implementation of the ReferenceCollection feature
  • Verify the implementation of Query-enabled DataSource

If you have a Feature Request, please post an issue.

Usage

For example..

@objcMembers
class User: Object {
    @objc enum UserType: Int {
        case normal
        case gold
        case premium        
    }
    dynamic var type: UserType = .normal
    dynamic var name: String?
    dynamic var thumbnail: File?
    dynamic var followers: ReferenceCollection<User> = []
    dynamic var items: NestedCollection<Item> = []
    
    // Custom property
    override func encode(_ key: String, value: Any?) -> Any? {
        if key == "type" {
            return self.type.rawValue
        }
        return nil
    }

    override func decode(_ key: String, value: Any?) -> Bool {
        if key == "type" {
            self.type = UserType(rawValue: value as! Int)
            return true
        }
        return false
    }
}
@objcMembers
class Item: Object {
    dynamic var thumbnail: File?
    dynamic var name: String? = "OWABIISHI"
}
// Set an arbitrary ID
let user: User = User(id: "ID")
user.save()
let userA: User = User()
userA.name = "userA"
userA.thumbnail = File(data: UIImageJPEGRepresentation(IMAGE, 0.3)!, mimeType: .jpeg)

let userB: User = User()
userB.name = "userB"
userB.thumbnail = File(data: UIImageJPEGRepresentation(IMAGE, 0.3)!, mimeType: .jpeg)

let item: Item = Item()
item.thumbnail = File(data: UIImageJPEGRepresentation(IMAGE, 0.3)!, mimeType: .jpeg)

userA.followers.insert(userB)
userA.items.insert(item)
userA.save()

Importantโ—๏ธ

Pring clearly separates save and update. This is to prevent unexpected overwriting. Pring provides three methods of initializing Object.

Initialization giving AutoID to Object

let user: User = User()

Initialization giving arbitrary ID

let user: User = User(id: "YOUR_ID") // isSaved false

Initialization when dealing with already saved Object

If you are dealing with an Object that has already been saved, please perform the following initialization. In case of this initialization can not save Please update.

let user: User = User(id: "YOUR_ID", value: [:]) // isSaved true

It is the developer's responsibility to manage the saved state of the Object.

Scheme

Pring inherits Object class and defines the Model. Pring supports many data types.

@objcMembers
class User: Object {
    dynamic var array: [String]                     = ["array"]
    dynamic var set: Set<String>                    = ["set"]
    dynamic var bool: Bool                          = true
    dynamic var binary: Data                        = "data".data(using: .utf8)!
    dynamic var file: File                          = File(data: UIImageJPEGRepresentation(UIImage(named: "")!, 1))
    dynamic var url: URL                            = URL(string: "https://firebase.google.com/")!
    dynamic var int: Int                            = Int.max
    dynamic var float: Double                       = Double.infinity
    dynamic var date: Date                          = Date(timeIntervalSince1970: 100)
    dynamic var geoPoint: GeoPoint                  = GeoPoint(latitude: 0, longitude: 0)
    dynamic var list: List<Group>                   = []    
    dynamic var dictionary: [String: Any]           = ["key": "value"]    
    dynamic var string: String                      = "string"
    
    let group: Reference<Group>                         = .init()
    let nestedCollection: NestedCollection<Item>   ใ€€ใ€€ใ€€ใ€€ใ€€ใ€€ใ€€ใ€€ใ€€ใ€€= []
    let referenceCollection: ReferenceCollection<User>  = []
}
DataType Description
Array It is Array type.
Set It is Set type.In Firestore it is expressed as {"value": true}.
Bool It is a boolean value.
File It is File type. You can save large data files.
URL It is URL type. It is saved as string in Firestore.
Int It is Int type.
Float It is Float type. In iOS, it will be a 64 bit Double type.
Date It is Date type.
GeoPoint It is GeoPoint type.
List It is Object array type.
Dictionary It is a Dictionary type. Save the structural data.
nestedCollection or referenceCollection It is SubCollection type.
String It is String type.
Reference It is Reference type. It hold DocumentReference
Null It is Null type.
Any It is custom type. You can specify it as a custom type if it is a class that inherits from NSObject.

โš ๏ธ Bool Int Float Double are not supported optional type.

โš™๏ธ Manage data

Save

Document can be saved only once.

let object: MyObject = MyObject()
object.save { (ref, error) in
   // completion
}

Retrieve

Retrieve document with ID.

MyObject.get(document!.id, block: { (document, error) in
    // do something
})

Update

Document has an update method. Be careful as it is different from Salada.

MyObject.get(document!.id, block: { (document, error) in
    document.string = "newString"
    document.update { error in
       // update
    }
})

Delete

Delete document with ID.

MyObject.get(document!.id, block: { (document, error) in
    document.delete()
})

Batched writes

let batch: WriteBatch = Firestore.firestore().batch()
batch.add(.save, object: userA) ย   //  ** File is not saved.
batch.add(.update, object: userB)
batch.add(.delete, object: userC)
batch.commit(completion: { (error) in
  // error handling
})

List

List can access the Object faster than NestedCollection. List holds data in Document, not SubCollection.

// save
let order: Order = Order()
do {
    let orderItem: OrderItem = OrderItem()
    orderItem.name = "aaaa"
    orderItem.price = 39
    order.items.append(orderItem)
}
do {
    let orderItem: OrderItem = OrderItem()
    orderItem.name = "bbb"
    orderItem.price = 21
    order.items.append(orderItem)
}
order.save()

Be sure to update the parent's object when updating data.

// update
Order.get("ORDER_ID") { (order, error) in
    order.items.first.name = "hoge"
    order.update()
}

๐Ÿ“„ File

Pring has a File class because it seamlessly works with Firebase Storage.

Save

File is saved with Document Save at the same time.

let object: MyObject = MyObject()
object.thumbnailImage = File(data: PNG_DATA, mimeType: .png)
let tasks: [String: StorageUploadTask] = object.save { (ref, error) in

}

save method returns the StorageUploadTask that is set with the key. For details on how to use StorageUploadTask, refer to Firebase docs.

let task: StorageUploadTask = tasks["thumbnailImage"]

Get data

Get data with size.

let task: StorageDownloadTask = object.thumbnail.getData(100000, block: { (data, error) in
    // do something
})

Update

If the Document is already saved, please use update method. update method also returns StorageUploadTask. Running update method automatically deletes old files.

let newFile: File = File(data: PNG_DATA, mimeType: .png)
object.thumbnailImage = newFile
object.update()

Delete

Delete it with delete method.

object.thumbnailImage = File.delete()
object.update()

If it is held in an array, automatic file deletion is done by deleting from the array and updating it.

object.files.remove(at: 0)
object.update()

Nested Collection & Reference Collection

NestedCollection and ReferenceCollection are classes that define SubCollection.

When holding File in SubCollection, saving of File will be executed first. When many Files are stored in SubCollection at once, the performance deteriorates.

Nested Collection

  • NestedCollection nests data and saves it under the document.
  • The destination path of File is nested path.

Reference Collection

  • ReferenceCollection saves the documentID under the document.
  • Data is saved separately.
@objcMembers
class User: Object {
    dynamic var name: String?
    dynamic var followers: ReferenceCollection<User> = []
    dynamic var items: NestedCollection<Item> = []
}

@objcMembers
class Item: Object {
    dynamic var thumbnail: File?
}

let userA: User = User()
userA.name = "userA"

let userB: User = User()
userB.name = "userB"

let item: Item = Item()
item.thumbnail = File(data: JPEG_DATA, mimeType: .jpeg)

userA.followers.insert(userB)
userA.items.insert(item)
userA.save()
let item: Item = Item()
userA.items.insert(item)
userA.update() { error in
  if let error = error {
    // error handling
    return
  }
  // do something
}

DataSource

DataSource is a class for easy handling of data retrieval from Collection.

class DataSourceViewController: UITableViewController {

    var dataSource: DataSource<User>?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.dataSource = User.order(by: \User.createdAt).limit(to: 30).dataSource()
            .on({ [weak self] (snapshot, changes) in
                guard let tableView: UITableView = self?.tableView else { return }
                switch changes {
                case .initial:
                    tableView.reloadData()
                case .update(let deletions, let insertions, let modifications):
                    tableView.beginUpdates()
                    tableView.insertRows(at: insertions.map { IndexPath(row: $0, section: 0) }, with: .automatic)
                    tableView.deleteRows(at: deletions.map { IndexPath(row: $0, section: 0) }, with: .automatic)
                    tableView.reloadRows(at: modifications.map { IndexPath(row: $0, section: 0) }, with: .automatic)
                    tableView.endUpdates()
                case .error(let error):
                    print(error)
                }
            }).listen()
    }

    // MARK: - Table view data source

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.dataSource?.count ?? 0
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell: DataSourceViewCell = tableView.dequeueReusableCell(withIdentifier: "DataSourceViewCell", for: indexPath) as! DataSourceViewCell
        configure(cell, atIndexPath: indexPath)
        return cell
    }

    func configure(_ cell: DataSourceViewCell, atIndexPath indexPath: IndexPath) {
        guard let user: User = self.dataSource?[indexPath.item] else { return }
        cell.textLabel?.text = user.name
        cell.disposer = user.listen { (user, error) in
            cell.textLabel?.text = user?.name
        }
    }

    func tableView(_ tableView: UITableView, didEndDisplaying cell: DataSourceViewCell, forRowAt indexPath: IndexPath) {
        cell.disposer?.dispose()
    }

    override func tableView(_ tableView: UITableView, canPerformAction action: Selector, forRowAt indexPath: IndexPath, withSender sender: Any?) -> Bool {
        return true
    }

    override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
        if editingStyle == .delete {
            self.dataSource?.removeDocument(at: indexPath.item)
        }
    }
}

SubCollection DataSource

User.get("USER_ID") { (user, error) in
    guard let user: User = user else { return }
    self.dataSource = user.followers.order(by: \User.createdAt).dataSource()
        .on { (snapshot, changes) in
            // something
        }.listen()
}

Synchronous Client Side Join

@objcMembers
class User: Object {

    let group: Reference<Group> = Reference()
}

Please add on(parse:) to DataSource.

self.dataSource = User.order(by: \User.updatedAt).dataSource()
    .on({ [weak self] (snapshot, changes) in
        guard let tableView: UITableView = self?.tableView else { return }
        debugPrint("On")
        switch changes {
        case .initial:
            tableView.reloadData()
        case .update(let deletions, let insertions, let modifications):
            tableView.beginUpdates()
            tableView.insertRows(at: insertions.map { IndexPath(row: $0, section: 0) }, with: .automatic)
            tableView.deleteRows(at: deletions.map { IndexPath(row: $0, section: 0) }, with: .automatic)
            tableView.reloadRows(at: modifications.map { IndexPath(row: $0, section: 0) }, with: .automatic)
            tableView.endUpdates()
        case .error(let error):
            print(error)
        }
    })
    .on(parse: { (snapshot, user, done) in
        user.group.get({ (group, error) in
            done(user)
        })
    })
    .onCompleted({ (snapshot, users) in
        debugPrint("completed")
    })
    .listen()

Query

Get documents

User.where(\User.name, isEqualTo: "name").get { (snapshot, error) in
    print(snapshot?.documents)
}

Get SubCollections

WHERE

let user: User = User(id: "user_id")
user.items.where(\Item.name, isEqualTo: "item_name").get { (snapshot, error) in
    print(snapshot?.documents)
}

ORDER

let user: User = User(id: "user_id")
user.items.order(by: \Item.updatedAt).get { (snapshot, error) in
    print(snapshot?.documents)
}

Create DataSource from Query

let user: User = User(id: "user_id")
user.items
    .where(\Item.name, isEqualTo: "item_name")
    .dataSource()
    .on({ (snapshot, change) in
        // do something
    })
    .onCompleted { (snapshot, items) in
        print(items)
}

Full-text search

Please use ElasticSearch or Algolia when performing full-text search on Firebase. There is a library when implementing with Swift.

https://github.com/miuP/Algent

More Repositories

1

Bleu

BLE (Bluetooth LE) for U๐ŸŽ Bleu is the best in the Bluetooth library.
Swift
489
star
2

Toolbar

Awesome autolayout Toolbar. Toolbar is a library for iOS. You can easily create chat InputBar.
Swift
456
star
3

Sumo

Sumo is a library that prepares for fast upload for iOS. It is effective when uploading by selecting images continuously.
Swift
241
star
4

Ballcap-iOS

Firebase Cloud Firestore support library for iOS. ๐Ÿงข
Swift
228
star
5

Salada

Firebase model framework Salada. Salada is the best Firebase framework.
Swift
225
star
6

PaperKit

PaperKit is like Paper app of Facebook
Objective-C
155
star
7

pring.ts

Cloud Firestore model framework for TypeScript - Google
TypeScript
109
star
8

ballcap.ts

Cloud Firestore support library for admin. ๐Ÿงข
TypeScript
107
star
9

Demae

TypeScript
77
star
10

Router

Router is a library that assists with SwiftUI view transitions.
Swift
74
star
11

AssemblyLine

AssemblyLine is a library for easily writing workflows.
Swift
40
star
12

Injectable

Dependency Injection for Swift
Ruby
31
star
13

Deck

Deck is a library that provides a UI to reproduce stacked cards for SwiftUI.
Swift
25
star
14

Muni

Chat with Cloud Firestore
Swift
23
star
15

TimeRangePicker

Swift
21
star
16

firestore-commerce

firestore-commerce is a framework that links Firestore and Stripe. By manipulating the Ballcap data model, you can sell immediately.
TypeScript
16
star
17

FirebaseAdmin

Firebase admin for Swift is a Swift package that provides a simple interface to interact with the Firebase admin SDK.
Swift
13
star
18

pring-admin.ts

Cloud Firestore model framework for TypeScript - Google
TypeScript
13
star
19

schedule.ts

TypeScript
9
star
20

SwiftWebUI-WASM-CFs

JavaScript
9
star
21

Messagestore

Swift
8
star
22

DocumentID

FirebaseFirestoreSwift's library for lightweight use of DocumentIDs in SwiftUI.
Swift
8
star
23

Flow

Swift
8
star
24

jp-zipcode

TypeScript
7
star
25

passkit.ts

Apple Pay, Wallet Development. passkit.ts is a library for issuing pass with typescript.
TypeScript
7
star
26

PickerGroup

Multi-picker for iOS and Mac available in SwiftUI
Swift
6
star
27

scenario

The scenario is the Cloud Functions support library. It is possible to clarify the dependency and limit the side effects.
TypeScript
6
star
28

tradable.ts

tradable.ts is a basic protocol to implement EC in Firebase.
TypeScript
5
star
29

Msg

Msg is a chat library based on FirebaseFirestore.
Swift
5
star
30

AdvancedTableViewSample

Advanced TableView Design Sample
Swift
5
star
31

OnTheKeyboard

Toolbar on the keyboard
Swift
5
star
32

CalendarUI

Swift
5
star
33

PaperKit-Camera

PaperKit + Camera is a super cool user interface that has integrated the camera to the UI of Paper.
Objective-C
5
star
34

StripeAPI

StripeAPI is a Framework that can handle Stripe type-safely.
Swift
4
star
35

FirestoreSwift

Swift
4
star
36

CameraUI

Swift
4
star
37

FirebaseAPI

Lightweight Cloud Firestore Client API using googleapis gRPC.
Swift
4
star
38

classy

classy provides getter / setter to typescript.
TypeScript
3
star
39

vue-pring-sample

Vue + Cloud Firestore +TypeScript
Vue
3
star
40

Antenna

A simple BLE sample code
Swift
3
star
41

Chart

Swift
3
star
42

SaladaSample

Salada sample code. Using Firebase Realtime Database
Swift
3
star
43

ReactionToolbar

ReactionToolbar is the UI, such as the Facebook of Ractions.
Swift
2
star
44

Tradable

Swift
2
star
45

flow.ts

Flow enables coding of structured scripts.
JavaScript
2
star
46

reaf

Host Next.js SSR app on Firebase Cloud Functions with Firebase Hosting redirects. Built with typescript.
TypeScript
2
star
47

ChatUI

Swift
2
star
48

MsgBox

MsgBox can build Chat by linking Firestore and Realm.
Swift
2
star
49

STPScrollView

STPScrollView is a Custom ScrollView
Objective-C
2
star
50

FirebaseInterface

Swift
2
star
51

tradestore.ts

TypeScript
2
star
52

RecurrenceRule

Swift
2
star
53

Socialbase

Socialbase is a framework for building SNS in Cloud Firestore.
Swift
2
star
54

STPPressGestureRecognizer

This is GestureRecognizer for iOS. It works like Force Touch.
Objective-C
2
star
55

ClockFace

Swift
2
star
56

ReactUI

JavaScript
2
star
57

FirebaseDemo

Firebase meetup #4
Ruby
2
star
58

Chat

Swift
1
star
59

MultiListener

Swift
1
star
60

FileSystemNavigator

Swift
1
star
61

SwiftUICell

SwiftUICell runs SwiftUI as CollectionView Cell
Swift
1
star
62

EventStack

Swift
1
star
63

Tong

Tong is library for using ElasticSearch with Swift.
Swift
1
star
64

Timeline

Swift
1
star
65

Calendar

Swift
1
star
66

Paym

Swift
1
star
67

document-propagator.ts

TypeScript
1
star
68

dressing

Dressing provides the functionality of CloudFunctions to connect Firebase and ElasticSearch. You need to use Salada for clients.
JavaScript
1
star
69

PHFetchedResultsController

A fetchedResultsController for PhotoKit. It can be divided into sections by date PhotoKit
Objective-C
1
star
70

NSMutableURLRequestMultipart

NSMutableURLRequestMultipart is a category of NSMutableURLRequest for sending a simple POST request.
Objective-C
1
star