• Stars
    star
    228
  • Rank 168,869 (Top 4 %)
  • Language
    Swift
  • License
    MIT License
  • Created over 5 years ago
  • Updated 11 months ago

Reviews

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

Repository Details

Firebase Cloud Firestore support library for iOS. ๐Ÿงข

๐Ÿงข Ballcap-iOS

Version Platform Downloads

Ballcap is a database schema design framework for Cloud Firestore.

Why Ballcap

Cloud Firestore is a great schema-less and flexible database that can handle data. However, its flexibility can create many bugs in development. Ballcap can assign schemas to Cloud Firestore to visualize data structures. This plays a very important role when developing as a team.

Inspired by https://github.com/firebase/firebase-ios-sdk/tree/pb-codable3

Please donate to continue development.

Sameple projects

Feature

โ˜‘๏ธ Firestore's document schema with Swift Codable
โ˜‘๏ธ Of course type safety.
โ˜‘๏ธ It seamlessly works with Firestore and Storage.

Requirements โ—๏ธ

Installation โš™

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

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

Usage

Document scheme

You must conform to the Codable and Modelable protocols to define Scheme.

struct Model: Codable, Equatable, Modelable {
    var number: Int = 0
    var string: String = "Ballcap"
}

Initialization

The document is initialized as follows:

// use auto id
let document: Document<Model> = Document()

print(document.data?.number) // 0
print(document.data?.string) // "Ballcap"

// KeyPath
print(document[\.number]) // 0
print(document[\.string]) // "Ballcap"


// Use your specified ID
let document: Document<Model> = Document(id: "YOUR_ID")

print(document.data?.number) // 0
print(document.data?.string) // "Ballcap"

// KeyPath
print(document[\.number]) // 0
print(document[\.string]) // "Ballcap"


// Use your specified DocumentReference
let documentReference: DocumentReference = Firestore.firestore().document("collection/document")
// note: If DocumentReference is specified, data is initialized with nil. 
let document: Document<Model> = Document(documentReference) 

print(document.data?.number) // nil
print(document.data?.string) // nil

// KeyPath
print(document[\.number]) // fail
print(document[\.string]) // fail

CRUD

Ballcap has a cache internally.When using the cache, use Batch instead of WriteBatch.

// save
document.save()

// update
document.update()

// delete
document.delete()

// Batch
let batch: Batch = Batch()
batch.save(document: document)
batch.update(document: document)
batch.delete(document: document)
batch.commit()

You can get data by using the get function.

Document<Model>.get(id: "DOCUMENT_ID", completion: { (document, error) in
    print(document.data)
})

The next get function gets data in favor of the cache. If there is no cached data, it gets from the server.

let document: Document<Model> = Document("DOCUMENT_ID")
document.get { (document, error) in
   print(document.data)
}

Why data is optional?

In CloudFirestore, DocumentReference does not necessarily have data. There are cases where there is no data under the following conditions.

  1. If no data is stored in DocumentReference.
  2. If data was acquired using Source.cache from DocumentReference, but there is no data in cache.

Ballcap recommends that developers unwrap if they can determine that there is data.

It is also possible to access the cache without using the network.

let document: Document<Model> = Document(id: "DOCUMENT_ID")
print(document.cache?.number) // 0
print(document.cache?.string) // "Ballcap"

Custom properties

Ballcap is preparing custom property to correspond to FieldValue.

ServerTimestamp

Property for handling FieldValue.serverTimestamp()

struct Model: Codable, Equatable {
    let serverValue: ServerTimestamp
    let localValue: ServerTimestamp
}
let model = Model(serverValue: .pending,
                  localValue: .resolved(Timestamp(seconds: 0, nanoseconds: 0)))

IncrementableInt & IncrementableDouble

Property for handling FieldValue.increment()

struct Model: Codable, Equatable, Modelable {
    var num: IncrementableInt = 0
}
let document: Document<Model> = Document()
document.data?.num = .increment(1)

OperableArray

Property for handling FieldValue.arrayRemove(), FieldValue.arrayUnion()

struct Model: Codable, Equatable, Modelable {
    var array: OperableArray<Int> = [0, 0]
}
let document: Document<Model> = Document()
document.data?.array = .arrayUnion([1])
document.data?.array = .arrayRemove([1])

File

File is a class for accessing Firestorage. You can save Data in the same path as Document by the follow:

let document: Document<Model> = Document(id: "DOCUMENT_ID")
let file: File = File(document.storageReference)

File supports multiple MIMETypes. Although File infers MIMEType from the name, it is better to input MIMEType explicitly.

  • plain
  • csv
  • html
  • css
  • javascript
  • octetStream(String?)
  • pdf
  • zip
  • tar
  • lzh
  • jpeg
  • pjpeg
  • png
  • gif
  • mp4
  • custom(String, String)

Upload & Download

Upload and Download each return a task. You can manage your progress by accessing tasks.

// upload
let ref: StorageReference = Storage.storage().reference().child("/a")
let data: Data = "test".data(using: .utf8)!
let file: File = File(ref, data: data, name: "n", mimeType: .plain)
let task = file.save { (metadata, error) in
    
}

// download
let task = file.getData(completion: { (data, error) in
    let text: String = String(data: data!, encoding: .utf8)!
})

StorageBatch

StorageBatch is used when uploading multiple files to Cloud Storage.

let textData: Data = "test".data(using: .utf8)!
let textFile: File = File(Storage.storage().reference(withPath: "c"), data: textData, mimeType: .plain)
batch.save(textFile)

let jpgData: Data = image.jpegData(compressionQuality: 1)!
let jpgFile: File = File(Storage.storage().reference(withPath: "d"), jpgData: textData, mimeType: .jpeg)
batch.save(jpgFile)
batch.commit { error in

}

DataSource

Ballcap provides a DataSource for easy handling of Collections and SubCollections.

DataSource initialize

from Document

let dataSource: DataSource<Item> = Document<Item>.query.dataSource()

from Collection Reference

CollectionReference

let query: DataSource<Document<Item>>.Query = DataSource.Query(Firestore.firestore().collection("items"))
let dataSource = DataSource(reference: query)

CollectionGroup

let query: DataSource<Document<Item>>.Query = DataSource.Query(Firestore.firestore().collectionGroup("items"))
let dataSource = DataSource(reference: query)

Your custom object

// from Custom class
let dataSource: DataSource<Item> = Item.query.dataSource()

// from CollectionReference
let query: DataSource<Item>.Query = DataSource.Query(Item.collectionReference)
let dataSource: DataSource<Item> = query.dataSource()

NSDiffableDataSourceSnapshot

self.dataSource = Document<Item>.query
    .order(by: "updatedAt", descending: true)
    .limit(to: 3)
    .dataSource()
    .retrieve(from: { (snapshot, documentSnapshot, done) in
        let document: Document<Item> = Document(documentSnapshot.reference)
        document.get { (item, error) in
            done(item!)
        }
    })
    .onChanged({ (snapshot, dataSourceSnapshot) in
        var snapshot: NSDiffableDataSourceSnapshot<Section, DocumentProxy<Item>> = self.tableViewDataSource.snapshot()
        snapshot.appendItems(dataSourceSnapshot.changes.insertions.map { DocumentProxy(document: $0)})
        snapshot.deleteItems(dataSourceSnapshot.changes.deletions.map { DocumentProxy(document: $0)})
        snapshot.reloadItems(dataSourceSnapshot.changes.modifications.map { DocumentProxy(document: $0)})
        self.tableViewDataSource.apply(snapshot, animatingDifferences: true)
    })
    .listen()

UITableViewDelegate, UITableViewDataSource

self.dataSource = Document<Item>.query
    .order(by: "updatedAt", descending: true)
    .limit(to: 3)
    .dataSource()
    .retrieve(from: { (snapshot, documentSnapshot, done) in
        let document: Document<Item> = Document(documentSnapshot.reference)
        document.get { (item, error) in
            done(item!)
        }
    })
    .onChanged({ (snapshot, dataSourceSnapshot) in
        self.tableView.performBatchUpdates({
            self.tableView.insertRows(at: dataSourceSnapshot.changes.insertions.map { IndexPath(item: dataSourceSnapshot.after.firstIndex(of: $0)!, section: 0)}, with: .automatic)
            self.tableView.deleteRows(at: dataSourceSnapshot.changes.deletions.map { IndexPath(item: dataSourceSnapshot.before.firstIndex(of: $0)!, section: 0)}, with: .automatic)
            self.tableView.reloadRows(at: dataSourceSnapshot.changes.modifications.map { IndexPath(item: dataSourceSnapshot.after.firstIndex(of: $0)!, section: 0)}, with: .automatic)
        }, completion: nil)
    })
    .listen()

Relationship between Document and Object

Document is a class that inherits Object. For simple operations, it is enough to use Document.

public final class Document<Model: Modelable & Codable>: Object, DataRepresentable, DataCacheable {

    public var data: Model?
    
}

You can perform complex operations by extending Object and defining your own class. Use examples are explained in Using Ballcap with SwiftUI

Migrate from Pring

Overview

The difference from Pring is that ReferenceCollection and NestedCollection have been abolished. In Pring, adding a child Object to the ReferenceCollection and NestedCollection of the parent Object saved the parent Object at the same time when it was saved. Ballcap requires the developer to save SubCollection using Batch. In addition, Pring also saved the File at the same time as the Object with the File was saved. Ballcap requires that developers save files using StorageBatch.

Scheme

Ballcap can handle Object class by inheriting Object class like Pring. If you inherit Object class, you must conform to DataRepresentable.

class Room: Object, DataRepresentable {

    var data: Model?

    struct Model: Modelable & Codable {
        var members: [String] = []
    }
}

SubCollection

Ballcap has discontinued NestedCollection and ReferenceCollection Class. Instead, it represents SubCollection by defining CollectionKeys.

Class must match HierarchicalStructurable to use CollectionKeys.

class Room: Object, DataRepresentable & HierarchicalStructurable {

    var data: Model?
    
    var transcripts: [Transcript] = []

    struct Model: Modelable & Codable {
        var members: [String] = []
    }

    enum CollectionKeys: String {
        case transcripts
    }
}

Use the collection function to access the SubCollection.

let collectionReference: CollectionReference = obj.collection(path: .transcripts)

SubCollection's Document save

let batch: Batch = Batch()
let room: Room = Room()
batch.save(room.transcripts, to: room.collection(path: .transcripts))
batch.commit()

Using Ballcap with SwiftUI

First, create an object that conforms to ObservableObject. DataListenable makes an Object observable.

final class User: Object, DataRepresentable, DataListenable, ObservableObject, Identifiable {

    typealias ID = String

    override class var name: String { "users" }

    struct Model: Codable, Modelable {

        var name: String = ""
    }

    @Published var data: User.Model?

    var listener: ListenerRegistration?
}

Next, create a View that displays this object.

struct UserView: View {

    @ObservedObject var user: User

    @State var isPresented: Bool = false

    var body: some View {
        VStack {
            Text(user[\.name])
        }
        .navigationBarTitle(Text("User"))
        .navigationBarItems(trailing: Button("Edit") {
            self.isPresented.toggle()
        })
        .sheet(isPresented: $isPresented) {
            UserEditView(user: self.user.copy(), isPresented: self.$isPresented)
        }
        .onAppear {
            _ = self.user.listen()
        }
    }
}

You can access the object data using subscript.

Text(user[\.name])

Start user observation with onAppear.

.onAppear {
    _ = self.user.listen()
}

Copy object

Pass a copy of Object to EditView before editing the data.

.sheet(isPresented: $isPresented) {
    UserEditView(user: self.user.copy(), isPresented: self.$isPresented)
}

Since the Object is being observed by the listener, changes can be caught automatically.

Finally, create a view that can update the object.

struct UserEditView: View {

    @ObservedObject var user: User

    @Binding var isPresented: Bool

    var body: some View {

        VStack {

            Form {
                Section(header: Text("Name")) {
                    TextField("Name", text: $user[\.name])
                }
            }

            Button("Save") {
                self.user.update()
                self.isPresented.toggle()
            }
        }.frame(height: 200)
    }
}

Updating an object is possible only with update().

Button("Update") {
    self.user.update()
    self.isPresented.toggle()
}

More Repositories

1

Bleu

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

Toolbar

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

Pring

Cloud Firestore model framework for iOS - Google
Swift
259
star
4

Sumo

Sumo is a library that prepares for fast upload for iOS. It is effective when uploading by selecting images continuously.
Swift
241
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
107
star
8

ballcap.ts

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

Demae

TypeScript
77
star
10

Router

Router is a library that assists with SwiftUI view transitions.
Swift
73
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
17
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

pring-admin.ts

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

FirebaseAdmin

Firebase admin for Swift is a Swift package that provides a simple interface to interact with the Firebase admin SDK.
Swift
10
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
6
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

PaperKit-Camera

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

Msg

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

AdvancedTableViewSample

Advanced TableView Design Sample
Swift
5
star
32

OnTheKeyboard

Toolbar on the keyboard
Swift
5
star
33

StripeAPI

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

CameraUI

Swift
4
star
35

FirebaseAPI

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

CalendarUI

Swift
4
star
37

classy

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

FirestoreSwift

Swift
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

flow.ts

Flow enables coding of structured scripts.
JavaScript
2
star
44

ReactionToolbar

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

Tradable

Swift
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

ReactUI

JavaScript
2
star
56

FirebaseDemo

Firebase meetup #4
Ruby
2
star
57

dressing

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

Chat

Swift
1
star
59

MultiListener

Swift
1
star
60

SwiftUICell

SwiftUICell runs SwiftUI as CollectionView Cell
Swift
1
star
61

EventStack

Swift
1
star
62

Tong

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

Timeline

Swift
1
star
64

Calendar

Swift
1
star
65

Paym

Swift
1
star
66

ClockFace

Swift
1
star
67

document-propagator.ts

TypeScript
1
star
68

PHFetchedResultsController

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

NSMutableURLRequestMultipart

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