Before you can use this extension, you'll need to update your security rules and add some code to your JavaScript app.
Update your Cloud Firestore security rules to allow lookups and writes to the _counter_shards_
subcollection where you want the extension to count. For example, to allow clients to increment the visits
field on any document in the pages
collection, you can write rules like this:
match /databases/{database}/documents/pages/{page} {
// Allow to increment only the 'visits' field and only by 1.
match /_counter_shards_/{shardId} {
allow get;
allow write: if request.resource.data.keys() == ["visits"]
&& (resource == null || request.resource.data.visits ==
resource.data.visits + 1);
}
}
-
Download and copy the compiled client sample into your application project.
-
Use the client sample in your code to increment counters. The code snippet below shows an example of how to use it. To see the full reference implementation, refer to the sample's TypeScript source code.
<html>
<head>
<script src="https://www.gstatic.com/firebasejs/[version]/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/[version]/firebase-firestore.js"></script>
<script src="sharded-counter.js"></script>
</head>
<body>
<script>
// Initialize Firebase.
var firebaseConfig = { projectId: "${param:PROJECT_ID}" };
firebase.initializeApp(firebaseConfig);
var db = firebase.firestore();
// Initialize the sharded counter.
var visits = new sharded.Counter(db.doc("pages/hello-world"), "visits");
// Increment the field "visits" of the document "pages/hello-world".
visits.incrementBy(1);
// Listen to locally consistent values.
visits.onSnapshot((snap) => {
console.log("Locally consistent view of visits: " + snap.data());
});
// Alternatively, if you don't mind counter delays, you can listen to the document directly.
db.doc("pages/hello-world").onSnapshot((snap) => {
console.log("Eventually consistent view of visits: " + snap.get("visits"));
});
</script>
</body>
</html>
<html>
<head> </head>
<body>
<script src="clients/web/dist/sharded-counter.js"></script>
<script type="module">
import { initializeApp } from "https://www.gstatic.com/firebasejs/9.6.1/firebase-app.js";
// Add Firebase products that you want to use
import { getAuth } from "https://www.gstatic.com/firebasejs/9.6.1/firebase-auth.js";
import {
getFirestore,
getDoc,
doc,
onSnapshot,
} from "https://www.gstatic.com/firebasejs/9.6.1/firebase-firestore.js";
// Initialize Firebase
const firebaseApp = initializeApp({ projectId: "extensions-testing" });
// initializeApp(firebaseConfig);
const db = getFirestore(firebaseApp);
const docRef = doc(db, "pages", "hello-world");
// Initialize the sharded counter.
var views = new sharded.Counter(docRef, "stats.views");
// // This will increment a field "stats.views" of the "pages/hello-world" document by 3.
views.incrementBy(4).then($ => console.log("returning document >>>>", $));
// // Listen to locally consistent values
views.onSnapshot(snap => {
console.log("Locally consistent view of visits: " + snap.data());
});
//Alternatively if you don't mind counter delays, you can listen to the document directly.
onSnapshot(doc(db, "pages", "hello-world"), snap => {
console.log(
"Eventually consistent view of visits: " + snap.get("stats.views")
);
});
</script>
</body>
</html>
-
Follow the steps in Add Firebase to your Android project to use Firebase in your app.
-
Copy the sample code to the directory in which you want to use the
FirestoreShardedCounter
instance.
import com.google.firebase.firestore.DocumentReference;
import com.google.firebase.firestore.DocumentSnapshot;
import com.google.firebase.firestore.EventListener;
import com.google.firebase.firestore.FirebaseFirestore;
import com.google.firebase.firestore.FirebaseFirestoreException;
import com.google.firebase.firestore.ListenerRegistration;
// somewhere in your app code initialize Firestore instance
FirebaseFirestore db = admin.firestore.getInstance();
// create reference to the collection and the document you wish to use
DocumentReference doc = db.collection("pages").document("hello-world");
// initialize FirestoreShardedCounter with the document and the property which will hold the counter value
FirestoreShardedCounter visits = new FirestoreShardedCounter(doc, "visits");
// to increment counter
visits.incrementBy(1);
// listen for updates
EventListener<Double> snapshotListener = new EventListener<Double>(){
@Override
public void onEvent(@Nullable Double value, @Nullable FirebaseFirestoreException error) {
// 'value' param is total amount of pages visits
}
};
ListenerRegistration registration = visits.onSnapshot(snapshotListener);
// clean up event listeners once finished
registration.remove();
// make one time call to query total amount of visits
double totalVisits = visits.get();
// if you don't mind counter delays, you can listen to the document directly.
db.document("pages/hello-world").addSnapshotListener(new EventListener<DocumentSnapshot>() {
@Override
public void onEvent(@Nullable DocumentSnapshot value, @Nullable FirebaseFirestoreException error) {
// total page visits
double pageVisits = (double) value.get("visits");
}
});
- Ensure your Swift app already has Firebase initialized.
- Copy and paste the sample code and create this file
FirestoreShardedCounter.swift
in the relevant directory you wish to use theFirestoreShardedCounter
instance.
import UIKit
import FirestoreCounter
import FirebaseFirestore
class ViewController: UIViewController {
// somewhere in your app code initialize Firestore instance
var db = Firestore.firestore()
// create reference to the collection and the document you wish to use
var doc = db.collection("pages").document("hello-world")
// initialize FirestoreShardedCounter with the document and the property which will hold the counter value
var controller = FirestoreShardCounter(docRef: doc, field: "visits")
override func viewDidLoad() {
super.viewDidLoad()
// event listener which returns total amount
controller.onSnapshot { (value, error) in
if let error = error {
// handle error
} else if let value = value {
// 'value' param is total amount of pages visits
}
}
}
@IBAction func getLatest(_ sender: Any) {
// get current total
controller.get() { (value, error) in
if let error = error {
// handle error
} else if let value = value {
// 'value' param is total amount of pages visits
}
}
}
@IBAction func incrementer(_ sender: Any) {
// to increment counter
controller.incrementBy(val: Double(1))
}
}
-
Follow the steps in Add Firebase Admin SDK to your server to use Firebase in your app.
-
Download and copy the Node.js Admin sample into your application project.
// Initialize Firebase.
const admin = require('firebase-admin');
admin.initializeApp();
const db = admin.firestore();
const Counter = require("./distributed_counter")
const visits = new Counter(db.collection("pages").doc("hello-world"), "visits")
// Increment the field "visits" of the document "pages/hello-world".
visits.incrementBy(1);
// Listen to locally consistent values.
visits.onSnapshot((snap) => {
console.log("Locally consistent view of visits: " + snap.data());
});
// Alternatively, if you don't mind counter delays, you can listen to the document directly.
db.collection("pages").doc("hello-world").onSnapshot((snap) => {
console.log("Eventually consistent view of visits: " + snap.get("visits"));
});
If you installed v0.1.3 or an earlier version of this extension, you set up a Cloud Scheduler job that either sent messages to the extension's Pub/Sub topic (${param:EXT_INSTANCE_ID}
) or called the extension's controller function. Starting in v0.1.4, the controllerCore function (${function:controllerCore.name}
) has a configurable schedule, so the manually-created Cloud Scheduler job is no longer required and will start to fail.
Delete the old Cloud Scheduler job on the Cloud Scheduler page of the Cloud console.
After you complete the post-installation configuration above, the process runs as follows:
-
Your extension creates subcollections in all the documents that your app uses as counters.
-
The client sample writes to these subcollections to distribute the write load.
-
The controllerCore function sums the subcollections' values into the single
visits
field (or whichever field you configured in your master document). -
After each summation, the extension deletes the subcollections, leaving only the count in the master document. This is the document field to which you should listen for the count.
As a best practice, you can monitor the activity of your installed extension, including checks on its health, usage, and logs.