Skip to content
This repository was archived by the owner on Apr 4, 2023. It is now read-only.

Commit 5becc0e

Browse files
Allow retrieving metadata with queries #1154
1 parent f839d75 commit 5becc0e

File tree

6 files changed

+136
-48
lines changed

6 files changed

+136
-48
lines changed

.travis.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ matrix:
2929
- os: osx
3030
env:
3131
- BuildiOS="12"
32-
- Xcode="10.0"
33-
osx_image: xcode10.0
32+
- Xcode="10.2"
33+
osx_image: "xcode10.2"
3434
language: node_js
3535
node_js: "8"
3636
jdk: oraclejdk8

demo-ng/app/tabs/firestore/firestore.component.ts

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -263,25 +263,38 @@ export class FirestoreComponent {
263263
firestoreDocumentObservable(): void {
264264
this.myCity$ = Observable.create(subscriber => {
265265
const docRef: firestore.DocumentReference = firebase.firestore().collection("cities").doc("SF");
266-
docRef.onSnapshot((doc: firestore.DocumentSnapshot) => {
267-
this.zone.run(() => {
268-
this.city = <City>doc.data();
269-
subscriber.next(this.city);
270-
});
271-
});
266+
docRef.onSnapshot(
267+
{includeMetadataChanges: true},
268+
(doc: firestore.DocumentSnapshot) => {
269+
270+
const source = doc.metadata.fromCache ? "local cache" : "server";
271+
console.log("Data came from " + source);
272+
console.log("Has pending writes? " + doc.metadata.hasPendingWrites);
273+
274+
this.zone.run(() => {
275+
this.city = <City>doc.data();
276+
subscriber.next(this.city);
277+
});
278+
});
272279
});
273280
}
274281

275282
firestoreCollectionObservable(): void {
276283
this.myCities$ = Observable.create(subscriber => {
277284
const colRef: firestore.CollectionReference = firebase.firestore().collection("cities");
278-
colRef.onSnapshot((snapshot: firestore.QuerySnapshot) => {
279-
this.zone.run(() => {
280-
this.cities = [];
281-
snapshot.forEach(docSnap => this.cities.push(<City>docSnap.data()));
282-
subscriber.next(this.cities);
283-
});
284-
});
285+
colRef.onSnapshot(
286+
{includeMetadataChanges: true},
287+
(snapshot: firestore.QuerySnapshot) => {
288+
const source = snapshot.metadata.fromCache ? "local cache" : "server";
289+
console.log("Data came from " + source);
290+
console.log("Has pending writes? " + snapshot.metadata.hasPendingWrites);
291+
292+
this.zone.run(() => {
293+
this.cities = [];
294+
snapshot.forEach(docSnap => this.cities.push(<City>docSnap.data()));
295+
subscriber.next(this.cities);
296+
});
297+
});
285298
});
286299
}
287300

docs/FIRESTORE.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,17 +64,21 @@ unsubscribe();
6464

6565
> Using **Observables**? [Check the example in the demo app](https://github.com/EddyVerbruggen/nativescript-plugin-firebase/blob/f6972433dea48bf1d342a6e4ef7f955dff341837/demo-ng/app/item/items.component.ts#L187-L198).
6666
67-
#### Determine if results are from cache or not
68-
If persistence is enabled, query results will first be returned from the local cache if available, and then from network. To determine if a document has been returned from the cache or not, pass the includeMetadataChanges parameter and inspect the metadata:
67+
#### Snapshot metadata (for queries and documents)
68+
Firestore can return metadata when passing the `includeMetadataChanges` boolean property. This can be used for:
69+
70+
- `snapshot.metadata.fromCache`: True if the snapshot was created from cached data rather than guaranteed up-to-date server data.
71+
- `snapshot.metadata.hasPendingWrites`: True if the snapshot contains the result of local writes that have not yet been committed to the backend.
6972

7073
```typescript
74+
import { firestore } from "nativescript-plugin-firebase";
7175
const citiesCollection = firebase.firestore().collection("cities");
7276

7377
const unsubscribe = citiesCollection.onSnapshot(({ includeMetadataChanges: true }, snapshot: firestore.QuerySnapshot) => {
7478
snapshot.forEach(city => console.log(city.data()));
7579

76-
const source = snapshot.metadata.fromCache ? "local cache" : "server";
77-
console.log("Data came from " + source);
80+
console.log("Data came from " + (snapshot.metadata.fromCache ? "local cache" : "server"));
81+
console.log("Has pending writes? " + snapshot.metadata.hasPendingWrites);
7882
});
7983

8084
// then after a while, to detach the listener:

src/firebase.android.ts

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ import { AndroidActivityResultEventData } from "tns-core-modules/application";
33
import lazy from "tns-core-modules/utils/lazy";
44
import { ad as AndroidUtils } from "tns-core-modules/utils/utils";
55
import {
6-
DataSnapshot, FBData, FBDataSingleEvent, FBErrorData, FirebaseQueryResult,
6+
DataSnapshot,
7+
FBDataSingleEvent,
8+
FBErrorData,
79
firestore,
810
GetAuthTokenOptions,
911
GetAuthTokenResult,
@@ -27,6 +29,11 @@ const gmsTasks = (<any>com.google.android.gms).tasks;
2729
class DocumentSnapshot extends DocumentSnapshotBase {
2830
android: com.google.firebase.firestore.DocumentSnapshot;
2931

32+
metadata = {
33+
fromCache: this.snapshot.getMetadata().isFromCache(),
34+
hasPendingWrites: this.snapshot.getMetadata().hasPendingWrites()
35+
};
36+
3037
constructor(public snapshot: com.google.firebase.firestore.DocumentSnapshot) {
3138
super(snapshot ? snapshot.getId() : null, snapshot.exists(), firebase.toJsObject(snapshot.getData()), convertDocRef(snapshot.getReference()));
3239
this.android = snapshot;
@@ -2229,7 +2236,7 @@ firebase.firestore.collection = (collectionPath: string): firestore.CollectionRe
22292236
where: (fieldPath: string, opStr: firestore.WhereFilterOp, value: any) => firebase.firestore.where(collectionPath, fieldPath, opStr, value),
22302237
orderBy: (fieldPath: string, directionStr: firestore.OrderByDirection): firestore.Query => firebase.firestore.orderBy(collectionPath, fieldPath, directionStr, collectionRef),
22312238
limit: (limit: number): firestore.Query => firebase.firestore.limit(collectionPath, limit, collectionRef),
2232-
onSnapshot: (optionsOrCallback: any, callback?: (snapshot: QuerySnapshot) => void) => firebase.firestore.onCollectionSnapshot(collectionRef, optionsOrCallback, callback),
2239+
onSnapshot: (optionsOrCallback: firestore.SnapshotListenOptions | ((snapshot: QuerySnapshot) => void), callback?: (snapshot: QuerySnapshot) => void) => firebase.firestore.onCollectionSnapshot(collectionRef, optionsOrCallback, callback),
22332240
startAfter: (snapshot: DocumentSnapshot): firestore.Query => firebase.firestore.startAfter(collectionPath, snapshot, collectionRef),
22342241
startAt: (snapshot: DocumentSnapshot): firestore.Query => firebase.firestore.startAt(collectionPath, snapshot, collectionRef),
22352242
endAt: (snapshot: DocumentSnapshot): firestore.Query => firebase.firestore.endAt(collectionPath, snapshot, collectionRef),
@@ -2242,9 +2249,16 @@ firebase.firestore.collection = (collectionPath: string): firestore.CollectionRe
22422249
}
22432250
};
22442251

2245-
firebase.firestore.onDocumentSnapshot = (docRef: com.google.firebase.firestore.DocumentReference, callback: (doc: DocumentSnapshot) => void): () => void => {
2246-
const listener = docRef.addSnapshotListener(new com.google.firebase.firestore.EventListener({
2247-
onEvent: ((snapshot: com.google.firebase.firestore.DocumentSnapshot, exception) => {
2252+
firebase.firestore.onDocumentSnapshot = (docRef: com.google.firebase.firestore.DocumentReference, optionsOrCallback: firestore.SnapshotListenOptions | ((snapshot: DocumentSnapshot) => void), callback: (doc: DocumentSnapshot) => void): () => void => {
2253+
let options = com.google.firebase.firestore.MetadataChanges.EXCLUDE;
2254+
if ((typeof optionsOrCallback) === "function") {
2255+
callback = <(snapshot: DocumentSnapshot) => void>optionsOrCallback;
2256+
} else if ((<firestore.SnapshotListenOptions>optionsOrCallback).includeMetadataChanges) {
2257+
options = com.google.firebase.firestore.MetadataChanges.INCLUDE;
2258+
}
2259+
2260+
const listener = docRef.addSnapshotListener(options, new com.google.firebase.firestore.EventListener({
2261+
onEvent: ((snapshot: com.google.firebase.firestore.DocumentSnapshot, exception: com.google.firebase.firestore.FirebaseFirestoreException) => {
22482262
if (exception === null) {
22492263
callback(new DocumentSnapshot(snapshot));
22502264
}
@@ -2255,11 +2269,11 @@ firebase.firestore.onDocumentSnapshot = (docRef: com.google.firebase.firestore.D
22552269
return () => listener.remove();
22562270
};
22572271

2258-
firebase.firestore.onCollectionSnapshot = (colRef: com.google.firebase.firestore.CollectionReference, optionsOrCallback: any, callback: (snapshot: QuerySnapshot) => void): () => void => {
2272+
firebase.firestore.onCollectionSnapshot = (colRef: com.google.firebase.firestore.CollectionReference, optionsOrCallback: firestore.SnapshotListenOptions | ((snapshot: QuerySnapshot) => void), callback: (snapshot: QuerySnapshot) => void): () => void => {
22592273
let options = com.google.firebase.firestore.MetadataChanges.EXCLUDE;
2260-
if ((typeof optionsOrCallback) === 'function') {
2261-
callback = optionsOrCallback;
2262-
} else if (optionsOrCallback.includeMetadataChanges) {
2274+
if ((typeof optionsOrCallback) === "function") {
2275+
callback = <(snapshot: QuerySnapshot) => void>optionsOrCallback;
2276+
} else if ((<firestore.SnapshotListenOptions>optionsOrCallback).includeMetadataChanges) {
22632277
options = com.google.firebase.firestore.MetadataChanges.INCLUDE;
22642278
}
22652279

@@ -2288,7 +2302,7 @@ firebase.firestore._getDocumentReference = (javaObj: JDocumentReference, collect
22882302
get: () => firebase.firestore.getDocument(collectionPath, javaObj.getId()),
22892303
update: (data: any) => firebase.firestore.update(collectionPath, javaObj.getId(), data),
22902304
delete: () => firebase.firestore.delete(collectionPath, javaObj.getId()),
2291-
onSnapshot: (callback: (doc: DocumentSnapshot) => void) => firebase.firestore.onDocumentSnapshot(javaObj, callback),
2305+
onSnapshot: (optionsOrCallback: firestore.SnapshotListenOptions | ((snapshot: DocumentSnapshot) => void), callback: (doc: DocumentSnapshot) => void) => firebase.firestore.onDocumentSnapshot(javaObj, optionsOrCallback, callback),
22922306
android: javaObj
22932307
};
22942308
};
@@ -2349,7 +2363,7 @@ firebase.firestore.add = (collectionPath: string, document: any): Promise<firest
23492363
get: () => firebase.firestore.getDocument(collectionPath, docRef.getId()),
23502364
update: (data: any) => firebase.firestore.update(collectionPath, docRef.getId(), data),
23512365
delete: () => firebase.firestore.delete(collectionPath, docRef.getId()),
2352-
onSnapshot: (callback: (doc: DocumentSnapshot) => void) => firebase.firestore.onDocumentSnapshot(docRef, callback)
2366+
onSnapshot: (optionsOrCallback: firestore.SnapshotListenOptions | ((snapshot: DocumentSnapshot) => void), callback: (doc: DocumentSnapshot) => void) => firebase.firestore.onDocumentSnapshot(docRef, optionsOrCallback, callback)
23532367
});
23542368
}
23552369
});
@@ -2565,7 +2579,7 @@ firebase.firestore._getQuery = (collectionPath: string, query: com.google.fireba
25652579
where: (fp: string, os: firestore.WhereFilterOp, v: any): firestore.Query => firebase.firestore.where(collectionPath, fp, os, v, query),
25662580
orderBy: (fp: string, directionStr: firestore.OrderByDirection): firestore.Query => firebase.firestore.orderBy(collectionPath, fp, directionStr, query),
25672581
limit: (limit: number): firestore.Query => firebase.firestore.limit(collectionPath, limit, query),
2568-
onSnapshot: (optionsOrCallback: any, callback?: (snapshot: QuerySnapshot) => void) => firebase.firestore.onCollectionSnapshot(query, optionsOrCallback, callback),
2582+
onSnapshot: (optionsOrCallback: firestore.SnapshotListenOptions | ((snapshot: QuerySnapshot) => void), callback?: (snapshot: QuerySnapshot) => void) => firebase.firestore.onCollectionSnapshot(query, optionsOrCallback, callback),
25692583
startAfter: (snapshot: DocumentSnapshot) => firebase.firestore.startAfter(collectionPath, snapshot, query),
25702584
startAt: (snapshot: DocumentSnapshot) => firebase.firestore.startAt(collectionPath, snapshot, query),
25712585
endAt: (snapshot: DocumentSnapshot) => firebase.firestore.endAt(collectionPath, snapshot, query),
@@ -2649,7 +2663,7 @@ function convertDocRef(docRef: JDocumentReference): firestore.DocumentReference
26492663
get: () => firebase.firestore.getDocument(collectionPath, docRef.getId()),
26502664
update: (data: any) => firebase.firestore.update(collectionPath, docRef.getId(), data),
26512665
delete: () => firebase.firestore.delete(collectionPath, docRef.getId()),
2652-
onSnapshot: (callback: (doc: DocumentSnapshot) => void) => firebase.firestore.onDocumentSnapshot(docRef, callback),
2666+
onSnapshot: (optionsOrCallback: firestore.SnapshotListenOptions | ((doc: DocumentSnapshot) => void), callback: (doc: DocumentSnapshot) => void) => firebase.firestore.onDocumentSnapshot(docRef, optionsOrCallback, callback),
26532667
android: docRef
26542668
};
26552669
}
@@ -2679,6 +2693,7 @@ export class QuerySnapshot implements firestore.QuerySnapshot {
26792693

26802694
metadata = {
26812695
fromCache: this.snapshot.getMetadata().isFromCache(),
2696+
hasPendingWrites: this.snapshot.getMetadata().hasPendingWrites()
26822697
};
26832698

26842699
get docs(): firestore.QueryDocumentSnapshot[] {

src/firebase.d.ts

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -679,6 +679,27 @@ export namespace firestore {
679679
merge?: boolean;
680680
}
681681

682+
export interface SnapshotMetadata {
683+
/**
684+
* True if the snapshot contains the result of local writes (e.g. set() or
685+
* update() calls) that have not yet been committed to the backend.
686+
* If your listener has opted into metadata updates (via
687+
* `DocumentListenOptions` or `QueryListenOptions`) you will receive another
688+
* snapshot with `hasPendingWrites` equal to false once the writes have been
689+
* committed to the backend.
690+
*/
691+
readonly hasPendingWrites: boolean;
692+
693+
/**
694+
* True if the snapshot was created from cached data rather than
695+
* guaranteed up-to-date server data. If your listener has opted into
696+
* metadata updates (via `DocumentListenOptions` or `QueryListenOptions`)
697+
* you will receive another snapshot with `fromCache` equal to false once
698+
* the client has received up-to-date data from the backend.
699+
*/
700+
readonly fromCache: boolean;
701+
}
702+
682703
export interface DocumentSnapshot {
683704
ios?: any;
684705
/* FIRDocumentSnapshot */
@@ -688,9 +709,22 @@ export namespace firestore {
688709
exists: boolean;
689710
ref: DocumentReference;
690711

712+
/**
713+
* Included when includeMetadataChanges is true.
714+
*/
715+
readonly metadata?: SnapshotMetadata;
716+
691717
data(): DocumentData;
692718
}
693719

720+
export interface SnapshotListenOptions {
721+
/**
722+
* Include a change even if only the metadata of the query or of a document changed.
723+
* Default false.
724+
*/
725+
readonly includeMetadataChanges?: boolean;
726+
}
727+
694728
export interface DocumentReference {
695729
discriminator: "docRef";
696730
id: string;
@@ -701,7 +735,7 @@ export namespace firestore {
701735
update: (document: any) => Promise<void>;
702736
delete: () => Promise<void>;
703737

704-
onSnapshot(callback: (doc: DocumentSnapshot) => void): () => void;
738+
onSnapshot(optionsOrCallback: SnapshotListenOptions | ((snapshot: DocumentSnapshot) => void), callback?: (snapshot: DocumentSnapshot) => void): () => void;
705739

706740
android?: any;
707741
ios?: any;
@@ -716,7 +750,7 @@ export namespace firestore {
716750

717751
limit(limit: number): Query;
718752

719-
onSnapshot(callback: (snapshot: QuerySnapshot) => void): () => void;
753+
onSnapshot(optionsOrCallback: SnapshotListenOptions | ((snapshot: QuerySnapshot) => void), callback?: (snapshot: QuerySnapshot) => void): () => void;
720754

721755
startAt(snapshot: DocumentSnapshot): Query;
722756

@@ -855,6 +889,11 @@ export namespace firestore {
855889
docSnapshots: firestore.DocumentSnapshot[];
856890
docs: firestore.QueryDocumentSnapshot[];
857891

892+
/**
893+
* Included when includeMetadataChanges is true.
894+
*/
895+
readonly metadata: SnapshotMetadata;
896+
858897
docChanges(options?: SnapshotListenOptions): DocumentChange[];
859898

860899
forEach(callback: (result: DocumentSnapshot) => void, thisArg?: any): void;

0 commit comments

Comments
 (0)