Skip to content

Commit e5828de

Browse files
authored
Merge pull request #488 from firebase/@invertase/swift-client
feat(firestore-counter): ios swift client
2 parents ff2bab0 + 21e248e commit e5828de

File tree

3 files changed

+179
-1
lines changed

3 files changed

+179
-1
lines changed

firestore-counter/POSTINSTALL.md

+53-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@ match /databases/{database}/documents/pages/{page} {
2020
```
2121

2222

23-
#### Specify a document path and increment value in your web app
23+
#### Client samples for incrementing counter and retrieving its value
24+
25+
##### Web Client
26+
2427

2528
1. Download and copy the [compiled client sample](https://github.com/firebase/extensions/blob/master/firestore-counter/clients/web/dist/sharded-counter.js) into your application project.
2629

@@ -60,6 +63,55 @@ match /databases/{database}/documents/pages/{page} {
6063
</html>
6164
```
6265

66+
##### iOS Client
67+
68+
1. Ensure your Swift app already has Firebase [initialized](https://firebase.google.com/docs/ios/setup).
69+
2. Copy and paste the sample [code](https://github.com/firebase/extensions/blob/next/firestore-counter/clients/ios/Sources/FirestoreCounter/FirestoreCounter.swift) and create this file `FirestoreShardedCounter.swift` in the relevant directory you wish to use the `FirestoreShardedCounter` instance.
70+
71+
```swift
72+
import UIKit
73+
import FirestoreCounter
74+
import FirebaseFirestore
75+
76+
class ViewController: UIViewController {
77+
// somewhere in your app code initialize Firestore instance
78+
var db = Firestore.firestore()
79+
// create reference to the collection and the document you wish to use
80+
var doc = db.collection("pages").document("hello-world")
81+
// initialize FirestoreShardedCounter with the document and the property which will hold the counter value
82+
var controller = FirestoreShardCounter(docRef: doc, field: "visits")
83+
84+
override func viewDidLoad() {
85+
super.viewDidLoad()
86+
// event listener which returns total amount
87+
controller.onSnapshot { (value, error) in
88+
if let error = error {
89+
// handle error
90+
} else if let value = value {
91+
// 'value' param is total amount of pages visits
92+
}
93+
}
94+
}
95+
96+
@IBAction func getLatest(_ sender: Any) {
97+
// get current total
98+
controller.get() { (value, error) in
99+
if let error = error {
100+
// handle error
101+
} else if let value = value {
102+
// 'value' param is total amount of pages visits
103+
}
104+
}
105+
}
106+
107+
@IBAction func incrementer(_ sender: Any) {
108+
// to increment counter
109+
controller.incrementBy(val: Double(1))
110+
}
111+
}
112+
113+
```
114+
63115

64116
#### Upgrading from v0.1.3 and earlier
65117

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// swift-tools-version:5.3
2+
// The swift-tools-version declares the minimum version of Swift required to build this package.
3+
4+
import PackageDescription
5+
6+
let package = Package(
7+
name: "FirestoreCounter",
8+
platforms: [
9+
.iOS(.v10)
10+
],
11+
products: [
12+
// Products define the executables and libraries a package produces, and make them visible to other packages.
13+
.library(
14+
name: "FirestoreCounter",
15+
targets: ["FirestoreCounter"]),
16+
],
17+
dependencies: [
18+
// Dependencies declare other packages that this package depends on.
19+
// .package(url: /* package url */, from: "1.0.0"),
20+
.package(name: "Firebase",
21+
url: "https://github.com/firebase/firebase-ios-sdk.git",
22+
.branch("7.0.0"))
23+
],
24+
targets: [
25+
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
26+
// Targets can depend on other targets in this package, and on products in packages this package depends on.
27+
.target(
28+
name: "FirestoreCounter",
29+
dependencies: [
30+
.product(name: "FirebaseFirestore", package: "Firebase")
31+
]),
32+
]
33+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import FirebaseFirestore
2+
import Firebase
3+
4+
public class FirebaseDistributedCounter {
5+
private let db: Firestore
6+
private let shardId = UUID().uuidString
7+
private var shards = [String: Double]()
8+
private let collectionId = "_counter_shards_"
9+
private let shardRef: CollectionReference
10+
private var docRef: DocumentReference
11+
private var field: String
12+
13+
public init(docRef: DocumentReference, field: String) {
14+
self.docRef = docRef
15+
self.field = field
16+
db = Firestore.firestore()
17+
shardRef = docRef.collection(collectionId)
18+
shards[self.docRef.path] = 0
19+
shards[shardRef.document(String(shardId)).path] = 0
20+
shards[shardRef.document("\t" + String(shardId.prefix(4))).path] = 0
21+
shards[shardRef.document("\t\t" + String(shardId.prefix(3))).path] = 0
22+
shards[shardRef.document("\t\t\t" + String(shardId.prefix(2))).path] = 0
23+
shards[shardRef.document("\t\t\t\t" + String(shardId.prefix(1))).path] = 0
24+
}
25+
26+
public func get(result: @escaping (Double?, Error?) -> Void) {
27+
var documentIds = [String]()
28+
29+
for key in shards.keys {
30+
documentIds.append((key as NSString).lastPathComponent)
31+
}
32+
33+
shardRef.whereField(FieldPath.documentID(), in: documentIds).getDocuments() { snapshot, error in
34+
if let error = error {
35+
print("FirestoreCounter: Error getting documents: \(error)")
36+
result(nil, error)
37+
} else {
38+
if let documents = snapshot?.documents {
39+
let values = documents.map { (documentSnap) -> Double in
40+
let document = documentSnap.data()
41+
if let amount = document[self.field] as? Double {
42+
return amount
43+
} else {
44+
return 0.0
45+
}
46+
}
47+
48+
let sum = values.reduce(0.0) { $0 + $1 }
49+
result(sum, nil)
50+
}
51+
}
52+
}
53+
}
54+
55+
public func increment(by delta: Double) {
56+
let increment = FieldValue.increment(delta)
57+
var array = field.components(separatedBy: ".")
58+
var update = [String: Any]()
59+
60+
array = array.reversed()
61+
62+
for component in array {
63+
if (update.isEmpty) {
64+
update = [component: increment]
65+
} else {
66+
update = [component: update]
67+
}
68+
}
69+
70+
shardRef.document(shardId).setData(update, merge: true)
71+
}
72+
73+
public func onSnapshot(_ listener: @escaping (Double?, Error?) -> Void) {
74+
shards.keys.forEach { path in
75+
db.document(path).addSnapshotListener { snapshot, error in
76+
if let error = error {
77+
print("FirestoreCounter: Error listening to document changes: \(error)")
78+
listener(nil, error)
79+
} else {
80+
guard let snapshot = snapshot,
81+
let snapshotValue = snapshot.get(self.field),
82+
let doubleVal = snapshotValue as? Double else {
83+
listener(nil, error)
84+
return
85+
}
86+
self.shards[path] = doubleVal
87+
let sum = self.shards.reduce(0.0) { $0 + $1.value }
88+
listener(sum, nil)
89+
}
90+
}
91+
}
92+
}
93+
}

0 commit comments

Comments
 (0)