2
0
mirror of synced 2025-01-31 08:34:53 +00:00

[android][firestore] cleanup

This commit is contained in:
Salakar 2018-08-01 23:00:40 +01:00
parent e7e89732c0
commit 0061ad9888
7 changed files with 566 additions and 318 deletions

View File

@ -1,20 +1,21 @@
package io.invertase.firebase; package io.invertase.firebase;
import android.annotation.SuppressLint;
import android.app.ActivityManager; import android.app.ActivityManager;
import android.content.Context; import android.content.Context;
import android.os.AsyncTask;
import android.util.Log; import android.util.Log;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.bridge.ReadableArray;
import javax.annotation.Nullable; import javax.annotation.Nullable;

View File

@ -52,18 +52,16 @@ public class FirestoreSerialize {
if (documentSnapshot.exists()) { if (documentSnapshot.exists()) {
documentMap.putMap(KEY_DATA, objectMapToWritable(documentSnapshot.getData())); documentMap.putMap(KEY_DATA, objectMapToWritable(documentSnapshot.getData()));
} }
// metadata // metadata
if (documentSnapshot.getMetadata() != null) {
WritableMap metadata = Arguments.createMap(); WritableMap metadata = Arguments.createMap();
metadata.putBoolean("fromCache", documentSnapshot.getMetadata().isFromCache()); metadata.putBoolean("fromCache", documentSnapshot.getMetadata().isFromCache());
metadata.putBoolean("hasPendingWrites", documentSnapshot.getMetadata().hasPendingWrites()); metadata.putBoolean("hasPendingWrites", documentSnapshot.getMetadata().hasPendingWrites());
documentMap.putMap(KEY_METADATA, metadata); documentMap.putMap(KEY_METADATA, metadata);
}
return documentMap; return documentMap;
} }
public static WritableMap snapshotToWritableMap(QuerySnapshot querySnapshot) { static WritableMap snapshotToWritableMap(QuerySnapshot querySnapshot) {
WritableMap queryMap = Arguments.createMap(); WritableMap queryMap = Arguments.createMap();
List<DocumentChange> documentChanges = querySnapshot.getDocumentChanges(); List<DocumentChange> documentChanges = querySnapshot.getDocumentChanges();
@ -78,12 +76,10 @@ public class FirestoreSerialize {
queryMap.putArray(KEY_DOCUMENTS, documents); queryMap.putArray(KEY_DOCUMENTS, documents);
// metadata // metadata
if (querySnapshot.getMetadata() != null) {
WritableMap metadata = Arguments.createMap(); WritableMap metadata = Arguments.createMap();
metadata.putBoolean("fromCache", querySnapshot.getMetadata().isFromCache()); metadata.putBoolean("fromCache", querySnapshot.getMetadata().isFromCache());
metadata.putBoolean("hasPendingWrites", querySnapshot.getMetadata().hasPendingWrites()); metadata.putBoolean("hasPendingWrites", querySnapshot.getMetadata().hasPendingWrites());
queryMap.putMap(KEY_METADATA, metadata); queryMap.putMap(KEY_METADATA, metadata);
}
return queryMap; return queryMap;
} }
@ -94,7 +90,7 @@ public class FirestoreSerialize {
* @param documentChanges List<DocumentChange> * @param documentChanges List<DocumentChange>
* @return WritableArray * @return WritableArray
*/ */
static WritableArray documentChangesToWritableArray(List<DocumentChange> documentChanges) { private static WritableArray documentChangesToWritableArray(List<DocumentChange> documentChanges) {
WritableArray documentChangesWritable = Arguments.createArray(); WritableArray documentChangesWritable = Arguments.createArray();
for (DocumentChange documentChange : documentChanges) { for (DocumentChange documentChange : documentChanges) {
documentChangesWritable.pushMap(documentChangeToWritableMap(documentChange)); documentChangesWritable.pushMap(documentChangeToWritableMap(documentChange));
@ -108,7 +104,7 @@ public class FirestoreSerialize {
* @param documentChange DocumentChange * @param documentChange DocumentChange
* @return WritableMap * @return WritableMap
*/ */
static WritableMap documentChangeToWritableMap(DocumentChange documentChange) { private static WritableMap documentChangeToWritableMap(DocumentChange documentChange) {
WritableMap documentChangeMap = Arguments.createMap(); WritableMap documentChangeMap = Arguments.createMap();
switch (documentChange.getType()) { switch (documentChange.getType()) {
@ -122,8 +118,10 @@ public class FirestoreSerialize {
documentChangeMap.putString(KEY_DOC_CHANGE_TYPE, "modified"); documentChangeMap.putString(KEY_DOC_CHANGE_TYPE, "modified");
} }
documentChangeMap.putMap(KEY_DOC_CHANGE_DOCUMENT, documentChangeMap.putMap(
snapshotToWritableMap(documentChange.getDocument())); KEY_DOC_CHANGE_DOCUMENT,
snapshotToWritableMap(documentChange.getDocument())
);
documentChangeMap.putInt(KEY_DOC_CHANGE_NEW_INDEX, documentChange.getNewIndex()); documentChangeMap.putInt(KEY_DOC_CHANGE_NEW_INDEX, documentChange.getNewIndex());
documentChangeMap.putInt(KEY_DOC_CHANGE_OLD_INDEX, documentChange.getOldIndex()); documentChangeMap.putInt(KEY_DOC_CHANGE_OLD_INDEX, documentChange.getOldIndex());
@ -136,7 +134,7 @@ public class FirestoreSerialize {
* @param map Map<String, Object> * @param map Map<String, Object>
* @return WritableMap * @return WritableMap
*/ */
static WritableMap objectMapToWritable(Map<String, Object> map) { private static WritableMap objectMapToWritable(Map<String, Object> map) {
WritableMap writableMap = Arguments.createMap(); WritableMap writableMap = Arguments.createMap();
for (Map.Entry<String, Object> entry : map.entrySet()) { for (Map.Entry<String, Object> entry : map.entrySet()) {
WritableMap typeMap = buildTypeMap(entry.getValue()); WritableMap typeMap = buildTypeMap(entry.getValue());
@ -224,7 +222,10 @@ public class FirestoreSerialize {
return typeMap; return typeMap;
} }
static Map<String, Object> parseReadableMap(FirebaseFirestore firestore, ReadableMap readableMap) { static Map<String, Object> parseReadableMap(
FirebaseFirestore firestore,
ReadableMap readableMap
) {
Map<String, Object> map = new HashMap<>(); Map<String, Object> map = new HashMap<>();
if (readableMap != null) { if (readableMap != null) {
ReadableMapKeySetIterator iterator = readableMap.keySetIterator(); ReadableMapKeySetIterator iterator = readableMap.keySetIterator();
@ -290,7 +291,10 @@ public class FirestoreSerialize {
} }
} }
public static List<Object> parseDocumentBatches(FirebaseFirestore firestore, ReadableArray readableArray) { static List<Object> parseDocumentBatches(
FirebaseFirestore firestore,
ReadableArray readableArray
) {
List<Object> writes = new ArrayList<>(readableArray.size()); List<Object> writes = new ArrayList<>(readableArray.size());
for (int i = 0; i < readableArray.size(); i++) { for (int i = 0; i < readableArray.size(); i++) {
Map<String, Object> write = new HashMap<>(); Map<String, Object> write = new HashMap<>();

View File

@ -18,13 +18,13 @@ import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener; import com.google.android.gms.tasks.OnSuccessListener;
import com.google.android.gms.tasks.Task; import com.google.android.gms.tasks.Task;
import com.google.firebase.FirebaseApp; import com.google.firebase.FirebaseApp;
import com.google.firebase.firestore.FirebaseFirestoreSettings;
import com.google.firebase.firestore.Transaction;
import com.google.firebase.firestore.DocumentReference; import com.google.firebase.firestore.DocumentReference;
import com.google.firebase.firestore.FieldValue; import com.google.firebase.firestore.FieldValue;
import com.google.firebase.firestore.FirebaseFirestore; import com.google.firebase.firestore.FirebaseFirestore;
import com.google.firebase.firestore.FirebaseFirestoreException; import com.google.firebase.firestore.FirebaseFirestoreException;
import com.google.firebase.firestore.FirebaseFirestoreSettings;
import com.google.firebase.firestore.SetOptions; import com.google.firebase.firestore.SetOptions;
import com.google.firebase.firestore.Transaction;
import com.google.firebase.firestore.WriteBatch; import com.google.firebase.firestore.WriteBatch;
import java.util.HashMap; import java.util.HashMap;
@ -48,6 +48,184 @@ public class RNFirebaseFirestore extends ReactContextBaseJavaModule {
* REACT NATIVE METHODS * REACT NATIVE METHODS
*/ */
/**
* Generates a js-like error from an exception and rejects the provided promise with it.
*
* @param exception Exception Exception normally from a task result.
* @param promise Promise react native promise
*/
static void promiseRejectException(Promise promise, FirebaseFirestoreException exception) {
WritableMap jsError = getJSError(exception);
promise.reject(
jsError.getString("code"),
jsError.getString("message"),
exception
);
}
/**
* Get a database instance for a specific firebase app instance
*
* @param appName appName
* @return FirebaseFirestore
*/
static FirebaseFirestore getFirestoreForApp(String appName) {
FirebaseApp firebaseApp = FirebaseApp.getInstance(appName);
return FirebaseFirestore.getInstance(firebaseApp);
}
/**
* Convert as firebase DatabaseError instance into a writable map
* with the correct web-like error codes.
*
* @param nativeException nativeException
* @return WritableMap
*/
static WritableMap getJSError(FirebaseFirestoreException nativeException) {
WritableMap errorMap = Arguments.createMap();
errorMap.putInt("nativeErrorCode", nativeException.getCode().value());
errorMap.putString("nativeErrorMessage", nativeException.getMessage());
String code;
String message;
String service = "Firestore";
// TODO: Proper error mappings
switch (nativeException.getCode()) {
case OK:
code = ErrorUtils.getCodeWithService(service, "ok");
message = ErrorUtils.getMessageWithService("Ok.", service, code);
break;
case CANCELLED:
code = ErrorUtils.getCodeWithService(service, "cancelled");
message = ErrorUtils.getMessageWithService("The operation was cancelled.", service, code);
break;
case UNKNOWN:
code = ErrorUtils.getCodeWithService(service, "unknown");
message = ErrorUtils.getMessageWithService(
"Unknown error or an error from a different error domain.",
service,
code
);
break;
case INVALID_ARGUMENT:
code = ErrorUtils.getCodeWithService(service, "invalid-argument");
message = ErrorUtils.getMessageWithService(
"Client specified an invalid argument.",
service,
code
);
break;
case DEADLINE_EXCEEDED:
code = ErrorUtils.getCodeWithService(service, "deadline-exceeded");
message = ErrorUtils.getMessageWithService(
"Deadline expired before operation could complete.",
service,
code
);
break;
case NOT_FOUND:
code = ErrorUtils.getCodeWithService(service, "not-found");
message = ErrorUtils.getMessageWithService(
"Some requested document was not found.",
service,
code
);
break;
case ALREADY_EXISTS:
code = ErrorUtils.getCodeWithService(service, "already-exists");
message = ErrorUtils.getMessageWithService(
"Some document that we attempted to create already exists.",
service,
code
);
break;
case PERMISSION_DENIED:
code = ErrorUtils.getCodeWithService(service, "permission-denied");
message = ErrorUtils.getMessageWithService(
"The caller does not have permission to execute the specified operation.",
service,
code
);
break;
case RESOURCE_EXHAUSTED:
code = ErrorUtils.getCodeWithService(service, "resource-exhausted");
message = ErrorUtils.getMessageWithService(
"Some resource has been exhausted, perhaps a per-user quota, or perhaps the entire file system is out of space.",
service,
code
);
break;
case FAILED_PRECONDITION:
code = ErrorUtils.getCodeWithService(service, "failed-precondition");
message = ErrorUtils.getMessageWithService(
"Operation was rejected because the system is not in a state required for the operation`s execution.",
service,
code
);
break;
case ABORTED:
code = ErrorUtils.getCodeWithService(service, "aborted");
message = ErrorUtils.getMessageWithService(
"The operation was aborted, typically due to a concurrency issue like transaction aborts, etc.",
service,
code
);
break;
case OUT_OF_RANGE:
code = ErrorUtils.getCodeWithService(service, "out-of-range");
message = ErrorUtils.getMessageWithService(
"Operation was attempted past the valid range.",
service,
code
);
break;
case UNIMPLEMENTED:
code = ErrorUtils.getCodeWithService(service, "unimplemented");
message = ErrorUtils.getMessageWithService(
"Operation is not implemented or not supported/enabled.",
service,
code
);
break;
case INTERNAL:
code = ErrorUtils.getCodeWithService(service, "internal");
message = ErrorUtils.getMessageWithService("Internal errors.", service, code);
break;
case UNAVAILABLE:
code = ErrorUtils.getCodeWithService(service, "unavailable");
message = ErrorUtils.getMessageWithService(
"The service is currently unavailable.",
service,
code
);
break;
case DATA_LOSS:
code = ErrorUtils.getCodeWithService(service, "data-loss");
message = ErrorUtils.getMessageWithService(
"Unrecoverable data loss or corruption.",
service,
code
);
break;
case UNAUTHENTICATED:
code = ErrorUtils.getCodeWithService(service, "unauthenticated");
message = ErrorUtils.getMessageWithService(
"The request does not have valid authentication credentials for the operation.",
service,
code
);
break;
default:
code = ErrorUtils.getCodeWithService(service, "unknown");
message = ErrorUtils.getMessageWithService("An unknown error occurred.", service, code);
}
errorMap.putString("code", code);
errorMap.putString("message", message);
return errorMap;
}
@ReactMethod @ReactMethod
public void disableNetwork(String appName, final Promise promise) { public void disableNetwork(String appName, final Promise promise) {
getFirestoreForApp(appName).disableNetwork().addOnCompleteListener(new OnCompleteListener<Void>() { getFirestoreForApp(appName).disableNetwork().addOnCompleteListener(new OnCompleteListener<Void>() {
@ -58,7 +236,10 @@ public class RNFirebaseFirestore extends ReactContextBaseJavaModule {
promise.resolve(null); promise.resolve(null);
} else { } else {
Log.e(TAG, "disableNetwork:onComplete:failure", task.getException()); Log.e(TAG, "disableNetwork:onComplete:failure", task.getException());
RNFirebaseFirestore.promiseRejectException(promise, (FirebaseFirestoreException)task.getException()); RNFirebaseFirestore.promiseRejectException(
promise,
(FirebaseFirestoreException) task.getException()
);
} }
} }
}); });
@ -83,35 +264,55 @@ public class RNFirebaseFirestore extends ReactContextBaseJavaModule {
promise.resolve(null); promise.resolve(null);
} else { } else {
Log.e(TAG, "enableNetwork:onComplete:failure", task.getException()); Log.e(TAG, "enableNetwork:onComplete:failure", task.getException());
RNFirebaseFirestore.promiseRejectException(promise, (FirebaseFirestoreException)task.getException()); RNFirebaseFirestore.promiseRejectException(
promise,
(FirebaseFirestoreException) task.getException()
);
} }
} }
}); });
} }
@ReactMethod @ReactMethod
public void collectionGet(String appName, String path, ReadableArray filters, public void collectionGet(
String appName, String path, ReadableArray filters,
ReadableArray orders, ReadableMap options, ReadableMap getOptions, ReadableArray orders, ReadableMap options, ReadableMap getOptions,
final Promise promise) { final Promise promise
RNFirebaseFirestoreCollectionReference ref = getCollectionForAppPath(appName, path, filters, orders, options); ) {
RNFirebaseFirestoreCollectionReference ref = getCollectionForAppPath(
appName,
path,
filters,
orders,
options
);
ref.get(getOptions, promise); ref.get(getOptions, promise);
} }
@ReactMethod @ReactMethod
public void collectionOffSnapshot(String appName, String path, ReadableArray filters, public void collectionOffSnapshot(
ReadableArray orders, ReadableMap options, String listenerId) { String appName, String path, ReadableArray filters,
ReadableArray orders, ReadableMap options, String listenerId
) {
RNFirebaseFirestoreCollectionReference.offSnapshot(listenerId); RNFirebaseFirestoreCollectionReference.offSnapshot(listenerId);
} }
@ReactMethod @ReactMethod
public void collectionOnSnapshot(String appName, String path, ReadableArray filters, public void collectionOnSnapshot(
String appName, String path, ReadableArray filters,
ReadableArray orders, ReadableMap options, String listenerId, ReadableArray orders, ReadableMap options, String listenerId,
ReadableMap queryListenOptions) { ReadableMap queryListenOptions
RNFirebaseFirestoreCollectionReference ref = getCollectionForAppPath(appName, path, filters, orders, options); ) {
RNFirebaseFirestoreCollectionReference ref = getCollectionForAppPath(
appName,
path,
filters,
orders,
options
);
ref.onSnapshot(listenerId, queryListenOptions); ref.onSnapshot(listenerId, queryListenOptions);
} }
@ReactMethod @ReactMethod
public void documentBatch(final String appName, final ReadableArray writes, public void documentBatch(final String appName, final ReadableArray writes,
final Promise promise) { final Promise promise) {
@ -166,7 +367,12 @@ public class RNFirebaseFirestore extends ReactContextBaseJavaModule {
} }
@ReactMethod @ReactMethod
public void documentGet(String appName, String path, ReadableMap getOptions, final Promise promise) { public void documentGet(
String appName,
String path,
ReadableMap getOptions,
final Promise promise
) {
RNFirebaseFirestoreDocumentReference ref = getDocumentForAppPath(appName, path); RNFirebaseFirestoreDocumentReference ref = getDocumentForAppPath(appName, path);
ref.get(getOptions, promise); ref.get(getOptions, promise);
} }
@ -177,18 +383,31 @@ public class RNFirebaseFirestore extends ReactContextBaseJavaModule {
} }
@ReactMethod @ReactMethod
public void documentOnSnapshot(String appName, String path, String listenerId, public void documentOnSnapshot(
ReadableMap docListenOptions) { String appName, String path, String listenerId,
ReadableMap docListenOptions
) {
RNFirebaseFirestoreDocumentReference ref = getDocumentForAppPath(appName, path); RNFirebaseFirestoreDocumentReference ref = getDocumentForAppPath(appName, path);
ref.onSnapshot(listenerId, docListenOptions); ref.onSnapshot(listenerId, docListenOptions);
} }
@ReactMethod @ReactMethod
public void documentSet(String appName, String path, ReadableMap data, ReadableMap options, final Promise promise) { public void documentSet(
String appName,
String path,
ReadableMap data,
ReadableMap options,
final Promise promise
) {
RNFirebaseFirestoreDocumentReference ref = getDocumentForAppPath(appName, path); RNFirebaseFirestoreDocumentReference ref = getDocumentForAppPath(appName, path);
ref.set(data, options, promise); ref.set(data, options, promise);
} }
/*
* Transaction Methods
*/
@ReactMethod @ReactMethod
public void documentUpdate(String appName, String path, ReadableMap data, final Promise promise) { public void documentUpdate(String appName, String path, ReadableMap data, final Promise promise) {
RNFirebaseFirestoreDocumentReference ref = getDocumentForAppPath(appName, path); RNFirebaseFirestoreDocumentReference ref = getDocumentForAppPath(appName, path);
@ -214,18 +433,17 @@ public class RNFirebaseFirestore extends ReactContextBaseJavaModule {
} else { } else {
firestoreSettings.setSslEnabled(firestore.getFirestoreSettings().isSslEnabled()); firestoreSettings.setSslEnabled(firestore.getFirestoreSettings().isSslEnabled());
} }
if (settings.hasKey("timestampsInSnapshots")) {
// TODO: Not supported on Android yet // if (settings.hasKey("timestampsInSnapshots")) {
} // // TODO: Not supported on Android yet
// }
firestore.setFirestoreSettings(firestoreSettings.build()); firestore.setFirestoreSettings(firestoreSettings.build());
promise.resolve(null); promise.resolve(null);
} }
/** /**
* Try clean up previous transactions on reload * Try clean up previous transactions on reload
*
*/ */
@Override @Override
public void onCatalystInstanceDestroy() { public void onCatalystInstanceDestroy() {
@ -239,23 +457,22 @@ public class RNFirebaseFirestore extends ReactContextBaseJavaModule {
transactionHandlers.clear(); transactionHandlers.clear();
} }
/*
* Transaction Methods
*/
/** /**
* Calls the internal Firestore Transaction classes instance .get(ref) method and resolves with * Calls the internal Firestore Transaction classes instance .get(ref) method and resolves with
* the DocumentSnapshot. * the DocumentSnapshot.
* *
* @param appName * @param appName appName
* @param transactionId * @param transactionId transactionId
* @param path * @param path path
* @param promise * @param promise promise
*/ */
@ReactMethod @ReactMethod
public void transactionGetDocument(String appName, int transactionId, String path, final Promise promise) { public void transactionGetDocument(
String appName,
int transactionId,
String path,
final Promise promise
) {
RNFirebaseFirestoreTransactionHandler handler = transactionHandlers.get(transactionId); RNFirebaseFirestoreTransactionHandler handler = transactionHandlers.get(transactionId);
if (handler == null) { if (handler == null) {
@ -269,11 +486,16 @@ public class RNFirebaseFirestore extends ReactContextBaseJavaModule {
} }
} }
/*
* INTERNALS/UTILS
*/
/** /**
* Aborts any pending signals and deletes the transaction handler. * Aborts any pending signals and deletes the transaction handler.
* *
* @param appName * @param appName appName
* @param transactionId * @param transactionId transactionId
*/ */
@ReactMethod @ReactMethod
public void transactionDispose(String appName, int transactionId) { public void transactionDispose(String appName, int transactionId) {
@ -288,12 +510,16 @@ public class RNFirebaseFirestore extends ReactContextBaseJavaModule {
/** /**
* Signals to transactionHandler that the command buffer is ready. * Signals to transactionHandler that the command buffer is ready.
* *
* @param appName * @param appName appName
* @param transactionId * @param transactionId transactionId
* @param commandBuffer * @param commandBuffer commandBuffer
*/ */
@ReactMethod @ReactMethod
public void transactionApplyBuffer(String appName, int transactionId, ReadableArray commandBuffer) { public void transactionApplyBuffer(
String appName,
int transactionId,
ReadableArray commandBuffer
) {
RNFirebaseFirestoreTransactionHandler handler = transactionHandlers.get(transactionId); RNFirebaseFirestoreTransactionHandler handler = transactionHandlers.get(transactionId);
if (handler != null) { if (handler != null) {
@ -304,12 +530,16 @@ public class RNFirebaseFirestore extends ReactContextBaseJavaModule {
/** /**
* Begin a new transaction via AsyncTask 's * Begin a new transaction via AsyncTask 's
* *
* @param appName * @param appName appName
* @param transactionId * @param transactionId transactionId
*/ */
@ReactMethod @ReactMethod
public void transactionBegin(final String appName, int transactionId) { public void transactionBegin(final String appName, int transactionId) {
final RNFirebaseFirestoreTransactionHandler transactionHandler = new RNFirebaseFirestoreTransactionHandler(appName, transactionId); final RNFirebaseFirestoreTransactionHandler transactionHandler = new RNFirebaseFirestoreTransactionHandler(
appName,
transactionId
);
transactionHandlers.put(transactionId, transactionHandler); transactionHandlers.put(transactionId, transactionHandler);
AsyncTask.execute(new Runnable() { AsyncTask.execute(new Runnable() {
@ -327,7 +557,11 @@ public class RNFirebaseFirestore extends ReactContextBaseJavaModule {
@Override @Override
public void run() { public void run() {
WritableMap eventMap = transactionHandler.createEventMap(null, "update"); WritableMap eventMap = transactionHandler.createEventMap(null, "update");
Utils.sendEvent(getReactApplicationContext(), "firestore_transaction_event", eventMap); Utils.sendEvent(
getReactApplicationContext(),
"firestore_transaction_event",
eventMap
);
} }
}); });
@ -336,12 +570,18 @@ public class RNFirebaseFirestore extends ReactContextBaseJavaModule {
// exit early if aborted - has to throw an exception otherwise will just keep trying ... // exit early if aborted - has to throw an exception otherwise will just keep trying ...
if (transactionHandler.aborted) { if (transactionHandler.aborted) {
throw new FirebaseFirestoreException("abort", FirebaseFirestoreException.Code.ABORTED); throw new FirebaseFirestoreException(
"abort",
FirebaseFirestoreException.Code.ABORTED
);
} }
// exit early if timeout from bridge - has to throw an exception otherwise will just keep trying ... // exit early if timeout from bridge - has to throw an exception otherwise will just keep trying ...
if (transactionHandler.timeout) { if (transactionHandler.timeout) {
throw new FirebaseFirestoreException("timeout", FirebaseFirestoreException.Code.DEADLINE_EXCEEDED); throw new FirebaseFirestoreException(
"timeout",
FirebaseFirestoreException.Code.DEADLINE_EXCEEDED
);
} }
// process any buffered commands from JS land // process any buffered commands from JS land
@ -357,15 +597,19 @@ public class RNFirebaseFirestore extends ReactContextBaseJavaModule {
ReadableMap command = buffer.getMap(i); ReadableMap command = buffer.getMap(i);
String path = command.getString("path"); String path = command.getString("path");
String type = command.getString("type"); String type = command.getString("type");
RNFirebaseFirestoreDocumentReference documentReference = getDocumentForAppPath(appName, path); RNFirebaseFirestoreDocumentReference documentReference = getDocumentForAppPath(
appName,
path
);
switch (type) { switch (type) {
case "set": case "set":
data = command.getMap("data"); data = command.getMap("data");
ReadableMap options = command.getMap("options"); ReadableMap options = command.getMap("options");
Map<String, Object> setData = FirestoreSerialize.parseReadableMap(RNFirebaseFirestore.getFirestoreForApp(appName), data); Map<String, Object> setData = FirestoreSerialize.parseReadableMap(
RNFirebaseFirestore.getFirestoreForApp(appName),
data
);
if (options != null && options.hasKey("merge") && options.getBoolean("merge")) { if (options != null && options.hasKey("merge") && options.getBoolean("merge")) {
transaction.set(documentReference.getRef(), setData, SetOptions.merge()); transaction.set(documentReference.getRef(), setData, SetOptions.merge());
@ -376,7 +620,11 @@ public class RNFirebaseFirestore extends ReactContextBaseJavaModule {
case "update": case "update":
data = command.getMap("data"); data = command.getMap("data");
Map<String, Object> updateData = FirestoreSerialize.parseReadableMap(RNFirebaseFirestore.getFirestoreForApp(appName), data); Map<String, Object> updateData = FirestoreSerialize.parseReadableMap(
RNFirebaseFirestore.getFirestoreForApp(appName),
data
);
transaction.update(documentReference.getRef(), updateData); transaction.update(documentReference.getRef(), updateData);
break; break;
case "delete": case "delete":
@ -396,7 +644,11 @@ public class RNFirebaseFirestore extends ReactContextBaseJavaModule {
if (!transactionHandler.aborted) { if (!transactionHandler.aborted) {
Log.d(TAG, "Transaction onSuccess!"); Log.d(TAG, "Transaction onSuccess!");
WritableMap eventMap = transactionHandler.createEventMap(null, "complete"); WritableMap eventMap = transactionHandler.createEventMap(null, "complete");
Utils.sendEvent(getReactApplicationContext(), "firestore_transaction_event", eventMap); Utils.sendEvent(
getReactApplicationContext(),
"firestore_transaction_event",
eventMap
);
} }
} }
}) })
@ -405,167 +657,60 @@ public class RNFirebaseFirestore extends ReactContextBaseJavaModule {
public void onFailure(@NonNull Exception e) { public void onFailure(@NonNull Exception e) {
if (!transactionHandler.aborted) { if (!transactionHandler.aborted) {
Log.w(TAG, "Transaction onFailure.", e); Log.w(TAG, "Transaction onFailure.", e);
WritableMap eventMap = transactionHandler.createEventMap((FirebaseFirestoreException) e, "error"); WritableMap eventMap = transactionHandler.createEventMap(
Utils.sendEvent(getReactApplicationContext(), "firestore_transaction_event", eventMap); (FirebaseFirestoreException) e,
} "error"
} );
}); Utils.sendEvent(
} getReactApplicationContext(),
}); "firestore_transaction_event",
} eventMap
/*
* INTERNALS/UTILS
*/
/**
* Generates a js-like error from an exception and rejects the provided promise with it.
*
* @param exception Exception Exception normally from a task result.
* @param promise Promise react native promise
*/
static void promiseRejectException(Promise promise, FirebaseFirestoreException exception) {
WritableMap jsError = getJSError(exception);
promise.reject(
jsError.getString("code"),
jsError.getString("message"),
exception
); );
} }
}
/** });
* Get a database instance for a specific firebase app instance }
* });
* @param appName
* @return
*/
static FirebaseFirestore getFirestoreForApp(String appName) {
FirebaseApp firebaseApp = FirebaseApp.getInstance(appName);
return FirebaseFirestore.getInstance(firebaseApp);
} }
/** /**
* Get a collection reference for a specific app and path * Get a collection reference for a specific app and path
* *
* @param appName * @param appName appName
* @param filters * @param filters filters
* @param orders * @param orders orders
* @param options * @param options options
* @param path @return * @param path @return
*/ */
private RNFirebaseFirestoreCollectionReference getCollectionForAppPath(String appName, String path, private RNFirebaseFirestoreCollectionReference getCollectionForAppPath(
String appName, String path,
ReadableArray filters, ReadableArray filters,
ReadableArray orders, ReadableArray orders,
ReadableMap options) { ReadableMap options
return new RNFirebaseFirestoreCollectionReference(this.getReactApplicationContext(), appName, path, filters, orders, options); ) {
return new RNFirebaseFirestoreCollectionReference(
this.getReactApplicationContext(),
appName,
path,
filters,
orders,
options
);
} }
/** /**
* Get a document reference for a specific app and path * Get a document reference for a specific app and path
* *
* @param appName * @param appName appName
* @param path * @param path path
* @return * @return RNFirebaseFirestoreDocumentReference
*/ */
private RNFirebaseFirestoreDocumentReference getDocumentForAppPath(String appName, String path) { private RNFirebaseFirestoreDocumentReference getDocumentForAppPath(String appName, String path) {
return new RNFirebaseFirestoreDocumentReference(this.getReactApplicationContext(), appName, path); return new RNFirebaseFirestoreDocumentReference(
} this.getReactApplicationContext(),
appName,
/** path
* Convert as firebase DatabaseError instance into a writable map );
* with the correct web-like error codes.
*
* @param nativeException
* @return
*/
static WritableMap getJSError(FirebaseFirestoreException nativeException) {
WritableMap errorMap = Arguments.createMap();
errorMap.putInt("nativeErrorCode", nativeException.getCode().value());
errorMap.putString("nativeErrorMessage", nativeException.getMessage());
String code;
String message;
String service = "Firestore";
// TODO: Proper error mappings
switch (nativeException.getCode()) {
case OK:
code = ErrorUtils.getCodeWithService(service, "ok");
message = ErrorUtils.getMessageWithService("Ok.", service, code);
break;
case CANCELLED:
code = ErrorUtils.getCodeWithService(service, "cancelled");
message = ErrorUtils.getMessageWithService("The operation was cancelled.", service, code);
break;
case UNKNOWN:
code = ErrorUtils.getCodeWithService(service, "unknown");
message = ErrorUtils.getMessageWithService("Unknown error or an error from a different error domain.", service, code);
break;
case INVALID_ARGUMENT:
code = ErrorUtils.getCodeWithService(service, "invalid-argument");
message = ErrorUtils.getMessageWithService("Client specified an invalid argument.", service, code);
break;
case DEADLINE_EXCEEDED:
code = ErrorUtils.getCodeWithService(service, "deadline-exceeded");
message = ErrorUtils.getMessageWithService("Deadline expired before operation could complete.", service, code);
break;
case NOT_FOUND:
code = ErrorUtils.getCodeWithService(service, "not-found");
message = ErrorUtils.getMessageWithService("Some requested document was not found.", service, code);
break;
case ALREADY_EXISTS:
code = ErrorUtils.getCodeWithService(service, "already-exists");
message = ErrorUtils.getMessageWithService("Some document that we attempted to create already exists.", service, code);
break;
case PERMISSION_DENIED:
code = ErrorUtils.getCodeWithService(service, "permission-denied");
message = ErrorUtils.getMessageWithService("The caller does not have permission to execute the specified operation.", service, code);
break;
case RESOURCE_EXHAUSTED:
code = ErrorUtils.getCodeWithService(service, "resource-exhausted");
message = ErrorUtils.getMessageWithService("Some resource has been exhausted, perhaps a per-user quota, or perhaps the entire file system is out of space.", service, code);
break;
case FAILED_PRECONDITION:
code = ErrorUtils.getCodeWithService(service, "failed-precondition");
message = ErrorUtils.getMessageWithService("Operation was rejected because the system is not in a state required for the operation`s execution.", service, code);
break;
case ABORTED:
code = ErrorUtils.getCodeWithService(service, "aborted");
message = ErrorUtils.getMessageWithService("The operation was aborted, typically due to a concurrency issue like transaction aborts, etc.", service, code);
break;
case OUT_OF_RANGE:
code = ErrorUtils.getCodeWithService(service, "out-of-range");
message = ErrorUtils.getMessageWithService("Operation was attempted past the valid range.", service, code);
break;
case UNIMPLEMENTED:
code = ErrorUtils.getCodeWithService(service, "unimplemented");
message = ErrorUtils.getMessageWithService("Operation is not implemented or not supported/enabled.", service, code);
break;
case INTERNAL:
code = ErrorUtils.getCodeWithService(service, "internal");
message = ErrorUtils.getMessageWithService("Internal errors.", service, code);
break;
case UNAVAILABLE:
code = ErrorUtils.getCodeWithService(service, "unavailable");
message = ErrorUtils.getMessageWithService("The service is currently unavailable.", service, code);
break;
case DATA_LOSS:
code = ErrorUtils.getCodeWithService(service, "data-loss");
message = ErrorUtils.getMessageWithService("Unrecoverable data loss or corruption.", service, code);
break;
case UNAUTHENTICATED:
code = ErrorUtils.getCodeWithService(service, "unauthenticated");
message = ErrorUtils.getMessageWithService("The request does not have valid authentication credentials for the operation.", service, code);
break;
default:
code = ErrorUtils.getCodeWithService(service, "unknown");
message = ErrorUtils.getMessageWithService("An unknown error occurred.", service, code);
}
errorMap.putString("code", code);
errorMap.putString("message", message);
return errorMap;
} }
/** /**

View File

@ -1,6 +1,7 @@
package io.invertase.firebase.firestore; package io.invertase.firebase.firestore;
import android.annotation.SuppressLint;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.util.Log; import android.util.Log;
@ -22,28 +23,32 @@ import com.google.firebase.firestore.Query;
import com.google.firebase.firestore.QuerySnapshot; import com.google.firebase.firestore.QuerySnapshot;
import com.google.firebase.firestore.Source; import com.google.firebase.firestore.Source;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import io.invertase.firebase.Utils; import io.invertase.firebase.Utils;
public class RNFirebaseFirestoreCollectionReference { class RNFirebaseFirestoreCollectionReference {
private static final String TAG = "RNFSCollectionReference"; private static final String TAG = "RNFSCollectionReference";
private static Map<String, ListenerRegistration> collectionSnapshotListeners = new HashMap<>(); private static Map<String, ListenerRegistration> collectionSnapshotListeners = new HashMap<>();
private final String appName;
private final String path; private final String path;
private final ReadableArray filters;
private final ReadableArray orders;
private final ReadableMap options;
private final Query query; private final Query query;
private final String appName;
private final ReadableMap options;
private final ReadableArray orders;
private final ReadableArray filters;
private ReactContext reactContext; private ReactContext reactContext;
RNFirebaseFirestoreCollectionReference(ReactContext reactContext, String appName, String path, RNFirebaseFirestoreCollectionReference(
ReadableArray filters, ReadableArray orders, ReactContext reactContext,
ReadableMap options) { String appName,
String path,
ReadableArray filters,
ReadableArray orders,
ReadableMap options
) {
this.appName = appName; this.appName = appName;
this.path = path; this.path = path;
this.filters = filters; this.filters = filters;
@ -53,6 +58,13 @@ public class RNFirebaseFirestoreCollectionReference {
this.reactContext = reactContext; this.reactContext = reactContext;
} }
static void offSnapshot(final String listenerId) {
ListenerRegistration listenerRegistration = collectionSnapshotListeners.remove(listenerId);
if (listenerRegistration != null) {
listenerRegistration.remove();
}
}
void get(ReadableMap getOptions, final Promise promise) { void get(ReadableMap getOptions, final Promise promise) {
Source source; Source source;
if (getOptions != null && getOptions.hasKey("source")) { if (getOptions != null && getOptions.hasKey("source")) {
@ -67,29 +79,34 @@ public class RNFirebaseFirestoreCollectionReference {
} else { } else {
source = Source.DEFAULT; source = Source.DEFAULT;
} }
@SuppressLint("StaticFieldLeak") final QuerySnapshotSerializeAsyncTask serializeAsyncTask = new QuerySnapshotSerializeAsyncTask(
reactContext, this
) {
@Override
protected void onPostExecute(WritableMap writableMap) {
promise.resolve(writableMap);
}
};
query.get(source).addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() { query.get(source).addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
@Override @Override
public void onComplete(@NonNull Task<QuerySnapshot> task) { public void onComplete(@NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) { if (task.isSuccessful()) {
Log.d(TAG, "get:onComplete:success"); Log.d(TAG, "get:onComplete:success");
WritableMap data = FirestoreSerialize.snapshotToWritableMap(task.getResult()); serializeAsyncTask.execute(task.getResult());
promise.resolve(data);
} else { } else {
Log.e(TAG, "get:onComplete:failure", task.getException()); Log.e(TAG, "get:onComplete:failure", task.getException());
RNFirebaseFirestore.promiseRejectException(promise, (FirebaseFirestoreException)task.getException()); RNFirebaseFirestore.promiseRejectException(
promise,
(FirebaseFirestoreException) task.getException()
);
} }
} }
}); });
} }
public static void offSnapshot(final String listenerId) { void onSnapshot(final String listenerId, final ReadableMap queryListenOptions) {
ListenerRegistration listenerRegistration = collectionSnapshotListeners.remove(listenerId);
if (listenerRegistration != null) {
listenerRegistration.remove();
}
}
public void onSnapshot(final String listenerId, final ReadableMap queryListenOptions) {
if (!collectionSnapshotListeners.containsKey(listenerId)) { if (!collectionSnapshotListeners.containsKey(listenerId)) {
final EventListener<QuerySnapshot> listener = new EventListener<QuerySnapshot>() { final EventListener<QuerySnapshot> listener = new EventListener<QuerySnapshot>() {
@Override @Override
@ -97,7 +114,8 @@ public class RNFirebaseFirestoreCollectionReference {
if (exception == null) { if (exception == null) {
handleQuerySnapshotEvent(listenerId, querySnapshot); handleQuerySnapshotEvent(listenerId, querySnapshot);
} else { } else {
ListenerRegistration listenerRegistration = collectionSnapshotListeners.remove(listenerId); ListenerRegistration listenerRegistration = collectionSnapshotListeners.remove(
listenerId);
if (listenerRegistration != null) { if (listenerRegistration != null) {
listenerRegistration.remove(); listenerRegistration.remove();
} }
@ -115,7 +133,10 @@ public class RNFirebaseFirestoreCollectionReference {
metadataChanges = MetadataChanges.EXCLUDE; metadataChanges = MetadataChanges.EXCLUDE;
} }
ListenerRegistration listenerRegistration = this.query.addSnapshotListener(metadataChanges, listener); ListenerRegistration listenerRegistration = this.query.addSnapshotListener(
metadataChanges,
listener
);
collectionSnapshotListeners.put(listenerId, listenerRegistration); collectionSnapshotListeners.put(listenerId, listenerRegistration);
} }
} }
@ -170,7 +191,7 @@ public class RNFirebaseFirestoreCollectionReference {
} else { } else {
ReadableArray fieldPathElements = fieldPathMap.getArray("elements"); ReadableArray fieldPathElements = fieldPathMap.getArray("elements");
String[] fieldPathArray = new String[fieldPathElements.size()]; String[] fieldPathArray = new String[fieldPathElements.size()];
for (int j=0; j<fieldPathElements.size(); j++) { for (int j = 0; j < fieldPathElements.size(); j++) {
fieldPathArray[j] = fieldPathElements.getString(j); fieldPathArray[j] = fieldPathElements.getString(j);
} }
FieldPath fieldPath = FieldPath.of(fieldPathArray); FieldPath fieldPath = FieldPath.of(fieldPathArray);
@ -202,13 +223,13 @@ public class RNFirebaseFirestoreCollectionReference {
Map<String, Object> order = (Map) o; Map<String, Object> order = (Map) o;
String direction = (String) order.get("direction"); String direction = (String) order.get("direction");
Map<String, Object> fieldPathMap = (Map) order.get("fieldPath"); Map<String, Object> fieldPathMap = (Map) order.get("fieldPath");
String fieldPathType = (String)fieldPathMap.get("type"); String fieldPathType = (String) fieldPathMap.get("type");
if (fieldPathType.equals("string")) { if (fieldPathType.equals("string")) {
String fieldPath = (String)fieldPathMap.get("string"); String fieldPath = (String) fieldPathMap.get("string");
query = query.orderBy(fieldPath, Query.Direction.valueOf(direction)); query = query.orderBy(fieldPath, Query.Direction.valueOf(direction));
} else { } else {
List<String> fieldPathElements = (List)fieldPathMap.get("elements"); List<String> fieldPathElements = (List) fieldPathMap.get("elements");
FieldPath fieldPath = FieldPath.of(fieldPathElements.toArray(new String[fieldPathElements.size()])); FieldPath fieldPath = FieldPath.of(fieldPathElements.toArray(new String[fieldPathElements.size()]));
query = query.orderBy(fieldPath, Query.Direction.valueOf(direction)); query = query.orderBy(fieldPath, Query.Direction.valueOf(direction));
} }
@ -218,57 +239,79 @@ public class RNFirebaseFirestoreCollectionReference {
private Query applyOptions(FirebaseFirestore firestore, Query query) { private Query applyOptions(FirebaseFirestore firestore, Query query) {
if (options.hasKey("endAt")) { if (options.hasKey("endAt")) {
List<Object> endAtList = FirestoreSerialize.parseReadableArray(firestore, options.getArray("endAt")); List<Object> endAtList = FirestoreSerialize.parseReadableArray(
firestore,
options.getArray("endAt")
);
query = query.endAt(endAtList.toArray()); query = query.endAt(endAtList.toArray());
} }
if (options.hasKey("endBefore")) { if (options.hasKey("endBefore")) {
List<Object> endBeforeList = FirestoreSerialize.parseReadableArray(firestore, options.getArray("endBefore")); List<Object> endBeforeList = FirestoreSerialize.parseReadableArray(
firestore,
options.getArray("endBefore")
);
query = query.endBefore(endBeforeList.toArray()); query = query.endBefore(endBeforeList.toArray());
} }
if (options.hasKey("limit")) { if (options.hasKey("limit")) {
int limit = options.getInt("limit"); int limit = options.getInt("limit");
query = query.limit(limit); query = query.limit(limit);
} }
if (options.hasKey("offset")) { // if (options.hasKey("offset")) {
// Android doesn't support offset // Android doesn't support offset
} // }
if (options.hasKey("selectFields")) { // if (options.hasKey("selectFields")) {
// Android doesn't support selectFields // Android doesn't support selectFields
} // }
if (options.hasKey("startAfter")) { if (options.hasKey("startAfter")) {
List<Object> startAfterList= FirestoreSerialize.parseReadableArray(firestore, options.getArray("startAfter")); List<Object> startAfterList = FirestoreSerialize.parseReadableArray(
firestore,
options.getArray("startAfter")
);
query = query.startAfter(startAfterList.toArray()); query = query.startAfter(startAfterList.toArray());
} }
if (options.hasKey("startAt")) { if (options.hasKey("startAt")) {
List<Object> startAtList= FirestoreSerialize.parseReadableArray(firestore, options.getArray("startAt")); List<Object> startAtList = FirestoreSerialize.parseReadableArray(
firestore,
options.getArray("startAt")
);
query = query.startAt(startAtList.toArray()); query = query.startAt(startAtList.toArray());
} }
return query; return query;
} }
/** /**
* Handles documentSnapshot events. * Handles documentSnapshot events.
* *
* @param listenerId * @param listenerId id
* @param querySnapshot * @param querySnapshot snapshot
*/ */
private void handleQuerySnapshotEvent(String listenerId, QuerySnapshot querySnapshot) { private void handleQuerySnapshotEvent(final String listenerId, QuerySnapshot querySnapshot) {
@SuppressLint("StaticFieldLeak") final QuerySnapshotSerializeAsyncTask serializeAsyncTask = new QuerySnapshotSerializeAsyncTask(
reactContext, this
) {
@Override
protected void onPostExecute(WritableMap writableMap) {
WritableMap event = Arguments.createMap(); WritableMap event = Arguments.createMap();
WritableMap data = FirestoreSerialize.snapshotToWritableMap(querySnapshot);
event.putString("appName", appName);
event.putString("path", path); event.putString("path", path);
event.putString("appName", appName);
event.putString("listenerId", listenerId); event.putString("listenerId", listenerId);
event.putMap("querySnapshot", data); event.putMap("querySnapshot", writableMap);
Utils.sendEvent(reactContext, "firestore_collection_sync_event", event); Utils.sendEvent(reactContext, "firestore_collection_sync_event", event);
} }
};
serializeAsyncTask.execute(querySnapshot);
}
/** /**
* Handles a documentSnapshot error event * Handles a documentSnapshot error event
* *
* @param listenerId * @param listenerId id
* @param exception * @param exception exception
*/ */
private void handleQuerySnapshotError(String listenerId, FirebaseFirestoreException exception) { private void handleQuerySnapshotError(String listenerId, FirebaseFirestoreException exception) {
WritableMap event = Arguments.createMap(); WritableMap event = Arguments.createMap();

View File

@ -1,5 +1,6 @@
package io.invertase.firebase.firestore; package io.invertase.firebase.firestore;
import android.annotation.SuppressLint;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.util.Log; import android.util.Log;
@ -31,17 +32,24 @@ public class RNFirebaseFirestoreDocumentReference {
private final String appName; private final String appName;
private final String path; private final String path;
private ReactContext reactContext;
private final DocumentReference ref; private final DocumentReference ref;
private ReactContext reactContext;
RNFirebaseFirestoreDocumentReference(ReactContext reactContext, String appName, String path) { RNFirebaseFirestoreDocumentReference(ReactContext reactContext, String appName, String path) {
this.appName = appName;
this.path = path; this.path = path;
this.appName = appName;
this.reactContext = reactContext; this.reactContext = reactContext;
this.ref = RNFirebaseFirestore.getFirestoreForApp(appName).document(path); this.ref = RNFirebaseFirestore.getFirestoreForApp(appName).document(path);
} }
public void delete(final Promise promise) { static void offSnapshot(final String listenerId) {
ListenerRegistration listenerRegistration = documentSnapshotListeners.remove(listenerId);
if (listenerRegistration != null) {
listenerRegistration.remove();
}
}
void delete(final Promise promise) {
this.ref.delete().addOnCompleteListener(new OnCompleteListener<Void>() { this.ref.delete().addOnCompleteListener(new OnCompleteListener<Void>() {
@Override @Override
public void onComplete(@NonNull Task<Void> task) { public void onComplete(@NonNull Task<Void> task) {
@ -50,7 +58,10 @@ public class RNFirebaseFirestoreDocumentReference {
promise.resolve(null); promise.resolve(null);
} else { } else {
Log.e(TAG, "delete:onComplete:failure", task.getException()); Log.e(TAG, "delete:onComplete:failure", task.getException());
RNFirebaseFirestore.promiseRejectException(promise, (FirebaseFirestoreException)task.getException()); RNFirebaseFirestore.promiseRejectException(
promise,
(FirebaseFirestoreException) task.getException()
);
} }
} }
}); });
@ -58,6 +69,7 @@ public class RNFirebaseFirestoreDocumentReference {
void get(final ReadableMap getOptions, final Promise promise) { void get(final ReadableMap getOptions, final Promise promise) {
Source source; Source source;
if (getOptions != null && getOptions.hasKey("source")) { if (getOptions != null && getOptions.hasKey("source")) {
String optionsSource = getOptions.getString("source"); String optionsSource = getOptions.getString("source");
if ("server".equals(optionsSource)) { if ("server".equals(optionsSource)) {
@ -70,45 +82,57 @@ public class RNFirebaseFirestoreDocumentReference {
} else { } else {
source = Source.DEFAULT; source = Source.DEFAULT;
} }
@SuppressLint("StaticFieldLeak") final DocumentSnapshotSerializeAsyncTask serializeAsyncTask = new DocumentSnapshotSerializeAsyncTask(
reactContext, this
) {
@Override
protected void onPostExecute(WritableMap writableMap) {
promise.resolve(writableMap);
}
};
this.ref.get(source).addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() { this.ref.get(source).addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
@Override @Override
public void onComplete(@NonNull Task<DocumentSnapshot> task) { public void onComplete(@NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()) { if (task.isSuccessful()) {
Log.d(TAG, "get:onComplete:success"); Log.d(TAG, "get:onComplete:success");
WritableMap data = FirestoreSerialize.snapshotToWritableMap(task.getResult()); serializeAsyncTask.execute(task.getResult());
promise.resolve(data);
} else { } else {
Log.e(TAG, "get:onComplete:failure", task.getException()); Log.e(TAG, "get:onComplete:failure", task.getException());
RNFirebaseFirestore.promiseRejectException(promise, (FirebaseFirestoreException)task.getException()); RNFirebaseFirestore.promiseRejectException(
promise,
(FirebaseFirestoreException) task.getException()
);
} }
} }
}); });
} }
public static void offSnapshot(final String listenerId) { void onSnapshot(final String listenerId, final ReadableMap docListenOptions) {
ListenerRegistration listenerRegistration = documentSnapshotListeners.remove(listenerId);
if (listenerRegistration != null) {
listenerRegistration.remove();
}
}
public void onSnapshot(final String listenerId, final ReadableMap docListenOptions) {
if (!documentSnapshotListeners.containsKey(listenerId)) { if (!documentSnapshotListeners.containsKey(listenerId)) {
final EventListener<DocumentSnapshot> listener = new EventListener<DocumentSnapshot>() { final EventListener<DocumentSnapshot> listener = new EventListener<DocumentSnapshot>() {
@Override @Override
public void onEvent(DocumentSnapshot documentSnapshot, FirebaseFirestoreException exception) { public void onEvent(
DocumentSnapshot documentSnapshot,
FirebaseFirestoreException exception
) {
if (exception == null) { if (exception == null) {
handleDocumentSnapshotEvent(listenerId, documentSnapshot); handleDocumentSnapshotEvent(listenerId, documentSnapshot);
} else { } else {
ListenerRegistration listenerRegistration = documentSnapshotListeners.remove(listenerId); ListenerRegistration listenerRegistration = documentSnapshotListeners.remove(listenerId);
if (listenerRegistration != null) { if (listenerRegistration != null) {
listenerRegistration.remove(); listenerRegistration.remove();
} }
handleDocumentSnapshotError(listenerId, exception); handleDocumentSnapshotError(listenerId, exception);
} }
} }
}; };
MetadataChanges metadataChanges; MetadataChanges metadataChanges;
if (docListenOptions != null if (docListenOptions != null
&& docListenOptions.hasKey("includeMetadataChanges") && docListenOptions.hasKey("includeMetadataChanges")
&& docListenOptions.getBoolean("includeMetadataChanges")) { && docListenOptions.getBoolean("includeMetadataChanges")) {
@ -116,19 +140,30 @@ public class RNFirebaseFirestoreDocumentReference {
} else { } else {
metadataChanges = MetadataChanges.EXCLUDE; metadataChanges = MetadataChanges.EXCLUDE;
} }
ListenerRegistration listenerRegistration = this.ref.addSnapshotListener(metadataChanges, listener);
ListenerRegistration listenerRegistration = this.ref.addSnapshotListener(
metadataChanges,
listener
);
documentSnapshotListeners.put(listenerId, listenerRegistration); documentSnapshotListeners.put(listenerId, listenerRegistration);
} }
} }
public void set(final ReadableMap data, final ReadableMap options, final Promise promise) { public void set(final ReadableMap data, final ReadableMap options, final Promise promise) {
Map<String, Object> map = FirestoreSerialize.parseReadableMap(RNFirebaseFirestore.getFirestoreForApp(appName), data);
Task<Void> task; Task<Void> task;
Map<String, Object> map = FirestoreSerialize.parseReadableMap(
RNFirebaseFirestore.getFirestoreForApp(appName),
data
);
if (options != null && options.hasKey("merge") && options.getBoolean("merge")) { if (options != null && options.hasKey("merge") && options.getBoolean("merge")) {
task = this.ref.set(map, SetOptions.merge()); task = this.ref.set(map, SetOptions.merge());
} else { } else {
task = this.ref.set(map); task = this.ref.set(map);
} }
task.addOnCompleteListener(new OnCompleteListener<Void>() { task.addOnCompleteListener(new OnCompleteListener<Void>() {
@Override @Override
public void onComplete(@NonNull Task<Void> task) { public void onComplete(@NonNull Task<Void> task) {
@ -137,14 +172,21 @@ public class RNFirebaseFirestoreDocumentReference {
promise.resolve(null); promise.resolve(null);
} else { } else {
Log.e(TAG, "set:onComplete:failure", task.getException()); Log.e(TAG, "set:onComplete:failure", task.getException());
RNFirebaseFirestore.promiseRejectException(promise, (FirebaseFirestoreException)task.getException()); RNFirebaseFirestore.promiseRejectException(
promise,
(FirebaseFirestoreException) task.getException()
);
} }
} }
}); });
} }
public void update(final ReadableMap data, final Promise promise) { void update(final ReadableMap data, final Promise promise) {
Map<String, Object> map = FirestoreSerialize.parseReadableMap(RNFirebaseFirestore.getFirestoreForApp(appName), data); Map<String, Object> map = FirestoreSerialize.parseReadableMap(
RNFirebaseFirestore.getFirestoreForApp(appName),
data
);
this.ref.update(map).addOnCompleteListener(new OnCompleteListener<Void>() { this.ref.update(map).addOnCompleteListener(new OnCompleteListener<Void>() {
@Override @Override
public void onComplete(@NonNull Task<Void> task) { public void onComplete(@NonNull Task<Void> task) {
@ -153,7 +195,10 @@ public class RNFirebaseFirestoreDocumentReference {
promise.resolve(null); promise.resolve(null);
} else { } else {
Log.e(TAG, "update:onComplete:failure", task.getException()); Log.e(TAG, "update:onComplete:failure", task.getException());
RNFirebaseFirestore.promiseRejectException(promise, (FirebaseFirestoreException)task.getException()); RNFirebaseFirestore.promiseRejectException(
promise,
(FirebaseFirestoreException) task.getException()
);
} }
} }
}); });
@ -163,7 +208,7 @@ public class RNFirebaseFirestoreDocumentReference {
* INTERNALS/UTILS * INTERNALS/UTILS
*/ */
public DocumentReference getRef() { DocumentReference getRef() {
return ref; return ref;
} }
@ -174,32 +219,44 @@ public class RNFirebaseFirestoreDocumentReference {
/** /**
* Handles documentSnapshot events. * Handles documentSnapshot events.
* *
* @param listenerId * @param listenerId id
* @param documentSnapshot * @param documentSnapshot snapshot
*/ */
private void handleDocumentSnapshotEvent(String listenerId, DocumentSnapshot documentSnapshot) { private void handleDocumentSnapshotEvent(
final String listenerId,
DocumentSnapshot documentSnapshot
) {
@SuppressLint("StaticFieldLeak") final DocumentSnapshotSerializeAsyncTask serializeAsyncTask = new DocumentSnapshotSerializeAsyncTask(
reactContext, this
) {
@Override
protected void onPostExecute(WritableMap writableMap) {
WritableMap event = Arguments.createMap(); WritableMap event = Arguments.createMap();
WritableMap data = FirestoreSerialize.snapshotToWritableMap(documentSnapshot);
event.putString("appName", appName);
event.putString("path", path); event.putString("path", path);
event.putString("appName", appName);
event.putString("listenerId", listenerId); event.putString("listenerId", listenerId);
event.putMap("documentSnapshot", data); event.putMap("documentSnapshot", writableMap);
Utils.sendEvent(reactContext, "firestore_document_sync_event", event); Utils.sendEvent(reactContext, "firestore_document_sync_event", event);
} }
};
serializeAsyncTask.execute(documentSnapshot);
}
/** /**
* Handles a documentSnapshot error event * Handles a documentSnapshot error event
* *
* @param listenerId * @param listenerId id
* @param exception * @param exception exception
*/ */
private void handleDocumentSnapshotError(String listenerId, FirebaseFirestoreException exception) { private void handleDocumentSnapshotError(
String listenerId,
FirebaseFirestoreException exception
) {
WritableMap event = Arguments.createMap(); WritableMap event = Arguments.createMap();
event.putString("appName", appName);
event.putString("path", path); event.putString("path", path);
event.putString("appName", appName);
event.putString("listenerId", listenerId); event.putString("listenerId", listenerId);
event.putMap("error", RNFirebaseFirestore.getJSError(exception)); event.putMap("error", RNFirebaseFirestore.getJSError(exception));

View File

@ -1,7 +1,6 @@
package io.invertase.firebase.firestore; package io.invertase.firebase.firestore;
import com.facebook.react.ReactPackage; import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.UIManagerModule; import com.facebook.react.uimanager.UIManagerModule;
@ -29,7 +28,7 @@ public class RNFirebaseFirestorePackage implements ReactPackage {
} }
/** /**
* @param reactContext * @param reactContext reactContext
* @return a list of view managers that should be registered with {@link UIManagerModule} * @return a list of view managers that should be registered with {@link UIManagerModule}
*/ */
@Override @Override

View File

@ -17,17 +17,16 @@ import javax.annotation.Nullable;
class RNFirebaseFirestoreTransactionHandler { class RNFirebaseFirestoreTransactionHandler {
private final ReentrantLock lock;
private final Condition condition;
boolean aborted = false;
boolean timeout = false;
private String appName; private String appName;
private long timeoutAt; private long timeoutAt;
private int transactionId; private int transactionId;
private final ReentrantLock lock;
private final Condition condition;
private ReadableArray commandBuffer; private ReadableArray commandBuffer;
private Transaction firestoreTransaction; private Transaction firestoreTransaction;
boolean aborted = false;
boolean timeout = false;
RNFirebaseFirestoreTransactionHandler(String app, int id) { RNFirebaseFirestoreTransactionHandler(String app, int id) {
appName = app; appName = app;
transactionId = id; transactionId = id;