Skip to content

Commit

Permalink
Implement serverTimestampBehavior on android
Browse files Browse the repository at this point in the history
  • Loading branch information
JanErikFoss committed Jul 13, 2021
1 parent 5039759 commit 748d782
Show file tree
Hide file tree
Showing 10 changed files with 72 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ Task<Void> settings(String appName, Map<String, Object> settings) {
UniversalFirebaseFirestoreStatics.FIRESTORE_SSL + "_" + appName, (boolean) settings.get("ssl"));
}

// settings.serverTimestampBehavior
if (settings.containsKey("serverTimestampBehavior")) {
UniversalFirebasePreferences.getSharedInstance().setStringValue(
UniversalFirebaseFirestoreStatics.FIRESTORE_SERVER_TIMESTAMP_BEHAVIOR + "_" + appName, (String) settings.get("serverTimestampBehavior"));
}

return null;
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ public class UniversalFirebaseFirestoreStatics {
public static String FIRESTORE_HOST = "firebase_firestore_host";
public static String FIRESTORE_PERSISTENCE = "firebase_firestore_persistence";
public static String FIRESTORE_SSL = "firebase_firestore_ssl";
public static String FIRESTORE_SERVER_TIMESTAMP_BEHAVIOR = "firebase_firestore_server_timestamp_behavior";
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ public void collectionOnSnapshot(

FirebaseFirestore firebaseFirestore = getFirestoreForApp(appName);
ReactNativeFirebaseFirestoreQuery firestoreQuery = new ReactNativeFirebaseFirestoreQuery(
appName,
getQueryForFirestore(firebaseFirestore, path, type),
filters,
orders,
Expand Down Expand Up @@ -128,6 +129,7 @@ public void collectionGet(
) {
FirebaseFirestore firebaseFirestore = getFirestoreForApp(appName);
ReactNativeFirebaseFirestoreQuery query = new ReactNativeFirebaseFirestoreQuery(
appName,
getQueryForFirestore(firebaseFirestore, path, type),
filters,
orders,
Expand Down Expand Up @@ -160,7 +162,7 @@ public void collectionGet(
}

private void sendOnSnapshotEvent(String appName, int listenerId, QuerySnapshot querySnapshot, MetadataChanges metadataChanges) {
Tasks.call(getTransactionalExecutor(Integer.toString(listenerId)), () -> snapshotToWritableMap("onSnapshot", querySnapshot, metadataChanges)).addOnCompleteListener(task -> {
Tasks.call(getTransactionalExecutor(Integer.toString(listenerId)), () -> snapshotToWritableMap(appName, "onSnapshot", querySnapshot, metadataChanges)).addOnCompleteListener(task -> {
if (task.isSuccessful()) {
WritableMap body = Arguments.createMap();
body.putMap("snapshot", task.getResult());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,11 @@


import com.facebook.react.bridge.Promise;
import com.google.firebase.firestore.DocumentSnapshot;
import com.google.firebase.firestore.FirebaseFirestoreException;

import io.invertase.firebase.common.UniversalFirebasePreferences;

import static io.invertase.firebase.common.ReactNativeFirebaseModule.rejectPromiseWithCodeAndMessage;
import static io.invertase.firebase.common.ReactNativeFirebaseModule.rejectPromiseWithExceptionMap;

Expand All @@ -39,4 +42,20 @@ static void rejectPromiseFirestoreException(Promise promise, Exception exception
rejectPromiseWithExceptionMap(promise, exception);
}
}

static DocumentSnapshot.ServerTimestampBehavior getServerTimestampBehavior(String appName) {
UniversalFirebasePreferences preferences = UniversalFirebasePreferences.getSharedInstance();
String key = UniversalFirebaseFirestoreStatics.FIRESTORE_SERVER_TIMESTAMP_BEHAVIOR + "_" + appName;
String behavior = preferences.getStringValue(key, "none");

if ("estimate".equals(behavior)) {
return DocumentSnapshot.ServerTimestampBehavior.ESTIMATE;
}

if ("previous".equals(behavior)) {
return DocumentSnapshot.ServerTimestampBehavior.PREVIOUS;
}

return DocumentSnapshot.ServerTimestampBehavior.NONE;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ public void documentGet(String appName, String path, ReadableMap getOptions, Pro

Tasks.call(getExecutor(), () -> {
DocumentSnapshot documentSnapshot = Tasks.await(documentReference.get(source));
return snapshotToWritableMap(documentSnapshot);
return snapshotToWritableMap(appName, documentSnapshot);
}).addOnCompleteListener(task -> {
if (task.isSuccessful()) {
promise.resolve(task.getResult());
Expand Down Expand Up @@ -261,7 +261,7 @@ public void documentBatch(String appName, ReadableArray writes, Promise promise)
}

private void sendOnSnapshotEvent(String appName, int listenerId, DocumentSnapshot documentSnapshot) {
Tasks.call(getExecutor(), () -> snapshotToWritableMap(documentSnapshot)).addOnCompleteListener(task -> {
Tasks.call(getExecutor(), () -> snapshotToWritableMap(appName, documentSnapshot)).addOnCompleteListener(task -> {
if (task.isSuccessful()) {
WritableMap body = Arguments.createMap();
body.putMap("snapshot", task.getResult());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,17 @@
import static io.invertase.firebase.firestore.ReactNativeFirebaseFirestoreSerialize.*;

public class ReactNativeFirebaseFirestoreQuery {
String appName;
Query query;

ReactNativeFirebaseFirestoreQuery(
String appName,
Query query,
ReadableArray filters,
ReadableArray orders,
ReadableMap options
) {
this.appName = appName;
this.query = query;
applyFilters(filters);
applyOrders(orders);
Expand All @@ -54,7 +57,7 @@ public class ReactNativeFirebaseFirestoreQuery {
public Task<WritableMap> get(Executor executor, Source source) {
return Tasks.call(executor, () -> {
QuerySnapshot querySnapshot = Tasks.await(query.get(source));
return snapshotToWritableMap("get", querySnapshot, null);
return snapshotToWritableMap(this.appName, "get", querySnapshot, null);
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@

import javax.annotation.Nullable;

import static io.invertase.firebase.firestore.ReactNativeFirebaseFirestoreCommon.getServerTimestampBehavior;
import static io.invertase.firebase.common.RCTConvertFirebase.toHashMap;

// public access for native re-use in brownfield apps
Expand Down Expand Up @@ -99,7 +100,7 @@ public class ReactNativeFirebaseFirestoreSerialize {
* @param documentSnapshot DocumentSnapshot
* @return WritableMap
*/
static WritableMap snapshotToWritableMap(DocumentSnapshot documentSnapshot) {
static WritableMap snapshotToWritableMap(String appName, DocumentSnapshot documentSnapshot) {
WritableArray metadata = Arguments.createArray();
WritableMap documentMap = Arguments.createMap();
SnapshotMetadata snapshotMetadata = documentSnapshot.getMetadata();
Expand All @@ -112,9 +113,11 @@ static WritableMap snapshotToWritableMap(DocumentSnapshot documentSnapshot) {
documentMap.putString(KEY_PATH, documentSnapshot.getReference().getPath());
documentMap.putBoolean(KEY_EXISTS, documentSnapshot.exists());

DocumentSnapshot.ServerTimestampBehavior timestampBehavior = getServerTimestampBehavior(appName);

if (documentSnapshot.exists()) {
if (documentSnapshot.getData() != null) {
documentMap.putMap(KEY_DATA, objectMapToWritable(documentSnapshot.getData()));
if (documentSnapshot.getData(timestampBehavior) != null) {
documentMap.putMap(KEY_DATA, objectMapToWritable(documentSnapshot.getData(timestampBehavior)));
}
}

Expand All @@ -127,7 +130,7 @@ static WritableMap snapshotToWritableMap(DocumentSnapshot documentSnapshot) {
* @param querySnapshot QuerySnapshot
* @return WritableMap
*/
static WritableMap snapshotToWritableMap(String source, QuerySnapshot querySnapshot, @Nullable MetadataChanges metadataChanges) {
static WritableMap snapshotToWritableMap(String appName, String source, QuerySnapshot querySnapshot, @Nullable MetadataChanges metadataChanges) {
WritableMap writableMap = Arguments.createMap();
writableMap.putString("source", source);

Expand All @@ -140,22 +143,22 @@ static WritableMap snapshotToWritableMap(String source, QuerySnapshot querySnaps
// If not listening to metadata changes, send the data back to JS land with a flag
// indicating the data does not include these changes
writableMap.putBoolean("excludesMetadataChanges", true);
writableMap.putArray(KEY_CHANGES, documentChangesToWritableArray(documentChangesList, null));
writableMap.putArray(KEY_CHANGES, documentChangesToWritableArray(appName, documentChangesList, null));
} else {
// If listening to metadata changes, get the changes list with document changes array.
// To indicate whether a document change was because of metadata change, we check whether
// its in the raw list by document key.
writableMap.putBoolean("excludesMetadataChanges", false);
List<DocumentChange> documentMetadataChangesList = querySnapshot.getDocumentChanges(MetadataChanges.INCLUDE);
writableMap.putArray(KEY_CHANGES, documentChangesToWritableArray(documentMetadataChangesList, documentChangesList));
writableMap.putArray(KEY_CHANGES, documentChangesToWritableArray(appName, documentMetadataChangesList, documentChangesList));
}

SnapshotMetadata snapshotMetadata = querySnapshot.getMetadata();
List<DocumentSnapshot> documentSnapshots = querySnapshot.getDocuments();

// set documents
for (DocumentSnapshot documentSnapshot : documentSnapshots) {
documents.pushMap(snapshotToWritableMap(documentSnapshot));
documents.pushMap(snapshotToWritableMap(appName, documentSnapshot));
}
writableMap.putArray(KEY_DOCUMENTS, documents);

Expand All @@ -174,7 +177,7 @@ static WritableMap snapshotToWritableMap(String source, QuerySnapshot querySnaps
* @param documentChanges List<DocumentChange>
* @return WritableArray
*/
private static WritableArray documentChangesToWritableArray(List<DocumentChange> documentChanges, @Nullable List<DocumentChange> comparableDocumentChanges) {
private static WritableArray documentChangesToWritableArray(String appName, List<DocumentChange> documentChanges, @Nullable List<DocumentChange> comparableDocumentChanges) {
WritableArray documentChangesWritable = Arguments.createArray();

boolean checkIfMetadataChange = comparableDocumentChanges != null;
Expand All @@ -191,7 +194,7 @@ private static WritableArray documentChangesToWritableArray(List<DocumentChange>
}
}

documentChangesWritable.pushMap(documentChangeToWritableMap(documentChange, isMetadataChange));
documentChangesWritable.pushMap(documentChangeToWritableMap(appName, documentChange, isMetadataChange));
}

return documentChangesWritable;
Expand All @@ -203,7 +206,7 @@ private static WritableArray documentChangesToWritableArray(List<DocumentChange>
* @param documentChange DocumentChange
* @return WritableMap
*/
private static WritableMap documentChangeToWritableMap(DocumentChange documentChange, boolean isMetadataChange) {
private static WritableMap documentChangeToWritableMap(String appName, DocumentChange documentChange, boolean isMetadataChange) {
WritableMap documentChangeMap = Arguments.createMap();
documentChangeMap.putBoolean("isMetadataChange", isMetadataChange);

Expand All @@ -221,7 +224,7 @@ private static WritableMap documentChangeToWritableMap(DocumentChange documentCh

documentChangeMap.putMap(
KEY_DOC_CHANGE_DOCUMENT,
snapshotToWritableMap(documentChange.getDocument())
snapshotToWritableMap(appName, documentChange.getDocument())
);

documentChangeMap.putInt(KEY_DOC_CHANGE_NEW_INDEX, documentChange.getNewIndex());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public void transactionGetDocument(String appName, int transactionId, String pat
DocumentReference documentReference = getDocumentForFirestore(firebaseFirestore, path);

Tasks
.call(getTransactionalExecutor(), () -> snapshotToWritableMap(transactionHandler.getDocument(documentReference)))
.call(getTransactionalExecutor(), () -> snapshotToWritableMap(appName, transactionHandler.getDocument(documentReference)))
.addOnCompleteListener(task -> {
if (task.isSuccessful()) {
promise.resolve(task.getResult());
Expand Down
14 changes: 14 additions & 0 deletions packages/firestore/lib/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1426,6 +1426,20 @@ export namespace FirebaseFirestoreTypes {
* If set to false or omitted, the SDK throws an exception when it encounters properties of type undefined.
*/
ignoreUndefinedProperties?: boolean;

/**
*
* If set, controls the return value for server timestamps that have not yet been set to their final value.
*
* By specifying 'estimate', pending server timestamps return an estimate based on the local clock.
* This estimate will differ from the final value and cause these values to change once the server result becomes available.
*
* By specifying 'previous', pending timestamps will be ignored and return their previous value instead.
*
* If omitted or set to 'none', null will be returned by default until the server value becomes available.
*
*/
serverTimestampBehavior?: 'estimate' | 'previous' | 'none';
}

/**
Expand Down
8 changes: 8 additions & 0 deletions packages/firestore/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,14 @@ class FirebaseFirestoreModule extends FirebaseModule {
throw new Error("firebase.firestore().settings(*) 'settings.ssl' must be a boolean value.");
}

if (!['estimate', 'previous', 'none'].includes(settings.serverTimestampBehavior)) {
return Promise.reject(
new Error(
"firebase.firestore().settings(*) 'settings.serverTimestampBehavior' must be one of 'estimate', 'previous', 'none'.",
)
);
}

if (!isUndefined(settings.ignoreUndefinedProperties)) {
if (!isBoolean(settings.ignoreUndefinedProperties)) {
return Promise.reject(
Expand Down

0 comments on commit 748d782

Please sign in to comment.