diff --git a/packages/database/android/src/reactnative/java/io/invertase/firebase/database/ReactNativeFirebaseDatabaseQueryModule.java b/packages/database/android/src/reactnative/java/io/invertase/firebase/database/ReactNativeFirebaseDatabaseQueryModule.java index 6a016b128c..da7c0409b8 100644 --- a/packages/database/android/src/reactnative/java/io/invertase/firebase/database/ReactNativeFirebaseDatabaseQueryModule.java +++ b/packages/database/android/src/reactnative/java/io/invertase/firebase/database/ReactNativeFirebaseDatabaseQueryModule.java @@ -43,8 +43,6 @@ public class ReactNativeFirebaseDatabaseQueryModule extends ReactNativeFirebaseM @Override public void invalidate() { - super.invalidate(); - Iterator refIterator = queryMap.entrySet().iterator(); while (refIterator.hasNext()) { Map.Entry pair = (Map.Entry) refIterator.next(); @@ -53,6 +51,8 @@ public void invalidate() { databaseQuery.removeAllEventListeners(); refIterator.remove(); // avoids a ConcurrentModificationException } + + super.invalidate(); } /** @@ -101,15 +101,19 @@ private void addOnceValueEventListener( new ValueEventListener() { @Override public void onDataChange(@Nonnull DataSnapshot dataSnapshot) { - Tasks.call(getExecutor(), () -> snapshotToMap(dataSnapshot)) - .addOnCompleteListener( - task -> { - if (task.isSuccessful()) { - promise.resolve(task.getResult()); - } else { - rejectPromiseWithExceptionMap(promise, task.getException()); - } - }); + try { + Tasks.call(getExecutor(), () -> snapshotToMap(dataSnapshot)) + .addOnCompleteListener( + task -> { + if (task.isSuccessful()) { + promise.resolve(task.getResult()); + } else { + rejectPromiseWithExceptionMap(promise, task.getException()); + } + }); + } catch (java.util.concurrent.RejectedExecutionException e) { + rejectPromiseWithExceptionMap(promise, e); + } } @Override @@ -139,17 +143,21 @@ private void addChildOnceEventListener( public void onChildAdded(@Nonnull DataSnapshot dataSnapshot, String previousChildName) { if ("child_added".equals(eventType)) { databaseQuery.removeEventListener(this); - Tasks.call( - getExecutor(), - () -> snapshotWithPreviousChildToMap(dataSnapshot, previousChildName)) - .addOnCompleteListener( - task -> { - if (task.isSuccessful()) { - promise.resolve(task.getResult()); - } else { - rejectPromiseWithExceptionMap(promise, task.getException()); - } - }); + try { + Tasks.call( + getExecutor(), + () -> snapshotWithPreviousChildToMap(dataSnapshot, previousChildName)) + .addOnCompleteListener( + task -> { + if (task.isSuccessful()) { + promise.resolve(task.getResult()); + } else { + rejectPromiseWithExceptionMap(promise, task.getException()); + } + }); + } catch (java.util.concurrent.RejectedExecutionException e) { + rejectPromiseWithExceptionMap(promise, e); + } } } @@ -157,17 +165,21 @@ public void onChildAdded(@Nonnull DataSnapshot dataSnapshot, String previousChil public void onChildChanged(@Nonnull DataSnapshot dataSnapshot, String previousChildName) { if ("child_changed".equals(eventType)) { databaseQuery.removeEventListener(this); - Tasks.call( - getExecutor(), - () -> snapshotWithPreviousChildToMap(dataSnapshot, previousChildName)) - .addOnCompleteListener( - task -> { - if (task.isSuccessful()) { - promise.resolve(task.getResult()); - } else { - rejectPromiseWithExceptionMap(promise, task.getException()); - } - }); + try { + Tasks.call( + getExecutor(), + () -> snapshotWithPreviousChildToMap(dataSnapshot, previousChildName)) + .addOnCompleteListener( + task -> { + if (task.isSuccessful()) { + promise.resolve(task.getResult()); + } else { + rejectPromiseWithExceptionMap(promise, task.getException()); + } + }); + } catch (java.util.concurrent.RejectedExecutionException e) { + rejectPromiseWithExceptionMap(promise, e); + } } } @@ -175,15 +187,19 @@ public void onChildChanged(@Nonnull DataSnapshot dataSnapshot, String previousCh public void onChildRemoved(@Nonnull DataSnapshot dataSnapshot) { if ("child_removed".equals(eventType)) { databaseQuery.removeEventListener(this); - Tasks.call(getExecutor(), () -> snapshotWithPreviousChildToMap(dataSnapshot, null)) - .addOnCompleteListener( - task -> { - if (task.isSuccessful()) { - promise.resolve(task.getResult()); - } else { - rejectPromiseWithExceptionMap(promise, task.getException()); - } - }); + try { + Tasks.call(getExecutor(), () -> snapshotWithPreviousChildToMap(dataSnapshot, null)) + .addOnCompleteListener( + task -> { + if (task.isSuccessful()) { + promise.resolve(task.getResult()); + } else { + rejectPromiseWithExceptionMap(promise, task.getException()); + } + }); + } catch (java.util.concurrent.RejectedExecutionException e) { + rejectPromiseWithExceptionMap(promise, e); + } } } @@ -191,17 +207,21 @@ public void onChildRemoved(@Nonnull DataSnapshot dataSnapshot) { public void onChildMoved(@Nonnull DataSnapshot dataSnapshot, String previousChildName) { if ("child_moved".equals(eventType)) { databaseQuery.removeEventListener(this); - Tasks.call( - getExecutor(), - () -> snapshotWithPreviousChildToMap(dataSnapshot, previousChildName)) - .addOnCompleteListener( - task -> { - if (task.isSuccessful()) { - promise.resolve(task.getResult()); - } else { - rejectPromiseWithExceptionMap(promise, task.getException()); - } - }); + try { + Tasks.call( + getExecutor(), + () -> snapshotWithPreviousChildToMap(dataSnapshot, previousChildName)) + .addOnCompleteListener( + task -> { + if (task.isSuccessful()) { + promise.resolve(task.getResult()); + } else { + rejectPromiseWithExceptionMap(promise, task.getException()); + } + }); + } catch (java.util.concurrent.RejectedExecutionException e) { + rejectPromiseWithExceptionMap(promise, e); + } } } @@ -315,34 +335,39 @@ private void handleDatabaseEvent( DataSnapshot dataSnapshot, @Nullable String previousChildName) { final String eventRegistrationKey = registration.getString("eventRegistrationKey"); - Tasks.call( - getTransactionalExecutor(eventRegistrationKey), - () -> { - if (eventType.equals("value")) { - return snapshotToMap(dataSnapshot); - } else { - return snapshotWithPreviousChildToMap(dataSnapshot, previousChildName); - } - }) - .addOnCompleteListener( - getExecutor(), - task -> { - if (task.isSuccessful()) { - WritableMap data = task.getResult(); - WritableMap event = Arguments.createMap(); - event.putMap("data", data); - event.putString("key", key); - event.putString("eventType", eventType); - event.putMap("registration", readableMapToWritableMap(registration)); - - ReactNativeFirebaseEventEmitter emitter = - ReactNativeFirebaseEventEmitter.getSharedInstance(); - - emitter.sendEvent( - new ReactNativeFirebaseDatabaseEvent( - ReactNativeFirebaseDatabaseEvent.EVENT_SYNC, event)); - } - }); + try { + Tasks.call( + getTransactionalExecutor(eventRegistrationKey), + () -> { + if (eventType.equals("value")) { + return snapshotToMap(dataSnapshot); + } else { + return snapshotWithPreviousChildToMap(dataSnapshot, previousChildName); + } + }) + .addOnCompleteListener( + getExecutor(), + task -> { + if (task.isSuccessful()) { + WritableMap data = task.getResult(); + WritableMap event = Arguments.createMap(); + event.putMap("data", data); + event.putString("key", key); + event.putString("eventType", eventType); + event.putMap("registration", readableMapToWritableMap(registration)); + + ReactNativeFirebaseEventEmitter emitter = + ReactNativeFirebaseEventEmitter.getSharedInstance(); + + emitter.sendEvent( + new ReactNativeFirebaseDatabaseEvent( + ReactNativeFirebaseDatabaseEvent.EVENT_SYNC, event)); + } + }); + } catch (java.util.concurrent.RejectedExecutionException e) { + // Event arrived after module invalidation shut down an executor. + // Safe to drop when tearing down; no JS listener will consume the event. + } } /** diff --git a/packages/firestore/android/src/reactnative/java/io/invertase/firebase/firestore/ReactNativeFirebaseFirestoreCollectionModule.java b/packages/firestore/android/src/reactnative/java/io/invertase/firebase/firestore/ReactNativeFirebaseFirestoreCollectionModule.java index 64ebb0266d..54a8c3c562 100644 --- a/packages/firestore/android/src/reactnative/java/io/invertase/firebase/firestore/ReactNativeFirebaseFirestoreCollectionModule.java +++ b/packages/firestore/android/src/reactnative/java/io/invertase/firebase/firestore/ReactNativeFirebaseFirestoreCollectionModule.java @@ -389,16 +389,20 @@ private void handleQueryOnSnapshot( private void handleQueryGet( ReactNativeFirebaseFirestoreQuery firestoreQuery, Source source, Promise promise) { - firestoreQuery - .get(getExecutor(), source) - .addOnCompleteListener( - task -> { - if (task.isSuccessful()) { - promise.resolve(task.getResult()); - } else { - rejectPromiseFirestoreException(promise, task.getException()); - } - }); + try { + firestoreQuery + .get(getExecutor(), source) + .addOnCompleteListener( + task -> { + if (task.isSuccessful()) { + promise.resolve(task.getResult()); + } else { + rejectPromiseFirestoreException(promise, task.getException()); + } + }); + } catch (java.util.concurrent.RejectedExecutionException e) { + rejectPromiseFirestoreException(promise, e); + } } private void sendOnSnapshotEvent( @@ -407,31 +411,36 @@ private void sendOnSnapshotEvent( int listenerId, QuerySnapshot querySnapshot, MetadataChanges metadataChanges) { - Tasks.call( - getTransactionalExecutor(Integer.toString(listenerId)), - () -> - snapshotToWritableMap( - appName, databaseId, "onSnapshot", querySnapshot, metadataChanges)) - .addOnCompleteListener( - task -> { - if (task.isSuccessful()) { - WritableMap body = Arguments.createMap(); - body.putMap("snapshot", task.getResult()); - - ReactNativeFirebaseEventEmitter emitter = - ReactNativeFirebaseEventEmitter.getSharedInstance(); - - emitter.sendEvent( - new ReactNativeFirebaseFirestoreEvent( - ReactNativeFirebaseFirestoreEvent.COLLECTION_EVENT_SYNC, - body, - appName, - databaseId, - listenerId)); - } else { - sendOnSnapshotError(appName, databaseId, listenerId, task.getException()); - } - }); + try { + Tasks.call( + getTransactionalExecutor(Integer.toString(listenerId)), + () -> + snapshotToWritableMap( + appName, databaseId, "onSnapshot", querySnapshot, metadataChanges)) + .addOnCompleteListener( + task -> { + if (task.isSuccessful()) { + WritableMap body = Arguments.createMap(); + body.putMap("snapshot", task.getResult()); + + ReactNativeFirebaseEventEmitter emitter = + ReactNativeFirebaseEventEmitter.getSharedInstance(); + + emitter.sendEvent( + new ReactNativeFirebaseFirestoreEvent( + ReactNativeFirebaseFirestoreEvent.COLLECTION_EVENT_SYNC, + body, + appName, + databaseId, + listenerId)); + } else { + sendOnSnapshotError(appName, databaseId, listenerId, task.getException()); + } + }); + } catch (java.util.concurrent.RejectedExecutionException e) { + // Snapshot arrived after module invalidation shut down the executor. + // Safe to drop — the module is being torn down and no JS listener remains. + } } private void sendOnSnapshotError( diff --git a/packages/firestore/android/src/reactnative/java/io/invertase/firebase/firestore/ReactNativeFirebaseFirestoreDocumentModule.java b/packages/firestore/android/src/reactnative/java/io/invertase/firebase/firestore/ReactNativeFirebaseFirestoreDocumentModule.java index 97d022c512..81473d98e0 100644 --- a/packages/firestore/android/src/reactnative/java/io/invertase/firebase/firestore/ReactNativeFirebaseFirestoreDocumentModule.java +++ b/packages/firestore/android/src/reactnative/java/io/invertase/firebase/firestore/ReactNativeFirebaseFirestoreDocumentModule.java @@ -300,27 +300,32 @@ public void documentBatch( private void sendOnSnapshotEvent( String appName, String databaseId, int listenerId, DocumentSnapshot documentSnapshot) { - Tasks.call(getExecutor(), () -> snapshotToWritableMap(appName, databaseId, documentSnapshot)) - .addOnCompleteListener( - task -> { - if (task.isSuccessful()) { - WritableMap body = Arguments.createMap(); - body.putMap("snapshot", task.getResult()); - - ReactNativeFirebaseEventEmitter emitter = - ReactNativeFirebaseEventEmitter.getSharedInstance(); - - emitter.sendEvent( - new ReactNativeFirebaseFirestoreEvent( - ReactNativeFirebaseFirestoreEvent.DOCUMENT_EVENT_SYNC, - body, - appName, - databaseId, - listenerId)); - } else { - sendOnSnapshotError(appName, databaseId, listenerId, task.getException()); - } - }); + try { + Tasks.call(getExecutor(), () -> snapshotToWritableMap(appName, databaseId, documentSnapshot)) + .addOnCompleteListener( + task -> { + if (task.isSuccessful()) { + WritableMap body = Arguments.createMap(); + body.putMap("snapshot", task.getResult()); + + ReactNativeFirebaseEventEmitter emitter = + ReactNativeFirebaseEventEmitter.getSharedInstance(); + + emitter.sendEvent( + new ReactNativeFirebaseFirestoreEvent( + ReactNativeFirebaseFirestoreEvent.DOCUMENT_EVENT_SYNC, + body, + appName, + databaseId, + listenerId)); + } else { + sendOnSnapshotError(appName, databaseId, listenerId, task.getException()); + } + }); + } catch (java.util.concurrent.RejectedExecutionException e) { + // Snapshot arrived after module invalidation shut down the executor. + // Safe to drop — the module is being torn down and no JS listener remains. + } } private void sendOnSnapshotError( diff --git a/packages/firestore/android/src/reactnative/java/io/invertase/firebase/firestore/ReactNativeFirebaseFirestoreTransactionModule.java b/packages/firestore/android/src/reactnative/java/io/invertase/firebase/firestore/ReactNativeFirebaseFirestoreTransactionModule.java index e22c0b4950..23fe331d5d 100644 --- a/packages/firestore/android/src/reactnative/java/io/invertase/firebase/firestore/ReactNativeFirebaseFirestoreTransactionModule.java +++ b/packages/firestore/android/src/reactnative/java/io/invertase/firebase/firestore/ReactNativeFirebaseFirestoreTransactionModule.java @@ -77,19 +77,23 @@ public void transactionGetDocument( FirebaseFirestore firebaseFirestore = getFirestoreForApp(appName, databaseId); DocumentReference documentReference = getDocumentForFirestore(firebaseFirestore, path); - Tasks.call( - getTransactionalExecutor(), - () -> - snapshotToWritableMap( - appName, databaseId, transactionHandler.getDocument(documentReference))) - .addOnCompleteListener( - task -> { - if (task.isSuccessful()) { - promise.resolve(task.getResult()); - } else { - rejectPromiseWithExceptionMap(promise, task.getException()); - } - }); + try { + Tasks.call( + getTransactionalExecutor(), + () -> + snapshotToWritableMap( + appName, databaseId, transactionHandler.getDocument(documentReference))) + .addOnCompleteListener( + task -> { + if (task.isSuccessful()) { + promise.resolve(task.getResult()); + } else { + rejectPromiseWithExceptionMap(promise, task.getException()); + } + }); + } catch (java.util.concurrent.RejectedExecutionException e) { + rejectPromiseWithExceptionMap(promise, e); + } } @ReactMethod