@react-native-firebase/database 26.3.3 → 27.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/RNFBDatabase.podspec +10 -2
  3. package/android/build.gradle +7 -0
  4. package/android/src/main/java/io/invertase/firebase/database/DatabaseAbortable.java +22 -0
  5. package/android/src/main/java/io/invertase/firebase/database/DatabaseQueryHandle.java +24 -0
  6. package/android/src/main/java/io/invertase/firebase/database/RNFBDatabaseKeepSyncedRegistry.java +106 -0
  7. package/android/src/main/java/io/invertase/firebase/database/RNFBDatabaseListenerRegistry.kt +133 -0
  8. package/android/src/main/java/io/invertase/firebase/database/RNFBDatabaseQueryRegistry.kt +76 -0
  9. package/android/src/main/java/io/invertase/firebase/database/RNFBDatabaseTransactionRegistry.kt +67 -0
  10. package/android/src/main/java/io/invertase/firebase/database/UniversalFirebaseDatabaseOnDisconnectModule.java +102 -9
  11. package/android/src/reactnative/java/io/invertase/firebase/database/NativeRNFBTurboDatabaseOnDisconnect.java +6 -11
  12. package/android/src/reactnative/java/io/invertase/firebase/database/NativeRNFBTurboDatabaseQuery.java +111 -24
  13. package/android/src/reactnative/java/io/invertase/firebase/database/NativeRNFBTurboDatabaseTransaction.java +12 -25
  14. package/android/src/reactnative/java/io/invertase/firebase/database/ReactNativeFirebaseDatabaseQuery.java +30 -33
  15. package/android/src/reactnative/java/io/invertase/firebase/database/ReactNativeFirebaseDatabaseTransactionHandler.java +3 -3
  16. package/android/src/reactnative/java/io/invertase/firebase/database/generated/java/com/facebook/fbreact/specs/NativeRNFBTurboDatabaseQuerySpec.java +4 -0
  17. package/android/src/reactnative/java/io/invertase/firebase/database/generated/jni/RNFBDatabaseTurboModules-generated.cpp +6 -0
  18. package/android/src/reactnative/java/io/invertase/firebase/database/generated/jni/react/renderer/components/RNFBDatabaseTurboModules/RNFBDatabaseTurboModulesJSI.h +12 -0
  19. package/android/src/test/java/io/invertase/firebase/database/RNFBDatabaseKeepSyncedRegistryTest.java +174 -0
  20. package/android/src/test/java/io/invertase/firebase/database/RNFBDatabaseListenerRegistryTest.java +191 -0
  21. package/android/src/test/java/io/invertase/firebase/database/RNFBDatabaseQueryRegistryTest.java +489 -0
  22. package/android/src/test/java/io/invertase/firebase/database/RNFBDatabaseTransactionRegistryTest.java +199 -0
  23. package/android/src/test/java/io/invertase/firebase/database/UniversalFirebaseDatabaseOnDisconnectCancelErrorTest.java +137 -0
  24. package/dist/module/DatabaseQuery.js +52 -1
  25. package/dist/module/DatabaseQuery.js.map +1 -1
  26. package/dist/module/DatabaseSyncTree.js +1 -1
  27. package/dist/module/DatabaseSyncTree.js.map +1 -1
  28. package/dist/module/index.js +1 -2
  29. package/dist/module/index.js.map +1 -1
  30. package/dist/module/modular/query.js +1 -1
  31. package/dist/module/modular/query.js.map +1 -1
  32. package/dist/module/version.js +1 -1
  33. package/dist/module/web/RNFBDatabaseModule.js +7 -0
  34. package/dist/module/web/RNFBDatabaseModule.js.map +1 -1
  35. package/dist/typescript/lib/DatabaseQuery.d.ts +25 -0
  36. package/dist/typescript/lib/DatabaseQuery.d.ts.map +1 -1
  37. package/dist/typescript/lib/index.d.ts +1 -1
  38. package/dist/typescript/lib/types/internal.d.ts +2 -0
  39. package/dist/typescript/lib/types/internal.d.ts.map +1 -1
  40. package/dist/typescript/lib/version.d.ts +1 -1
  41. package/dist/typescript/lib/web/RNFBDatabaseModule.d.ts.map +1 -1
  42. package/ios/RNFBDatabase/RNFBDatabaseListenerRegistry.h +37 -0
  43. package/ios/RNFBDatabase/RNFBDatabaseListenerRegistry.m +91 -0
  44. package/ios/RNFBDatabase/RNFBDatabaseListenerRegistryStorage.swift +81 -0
  45. package/ios/RNFBDatabase/RNFBDatabaseNullSentinelDecoder.h +36 -0
  46. package/ios/RNFBDatabase/RNFBDatabaseNullSentinelDecoder.m +48 -0
  47. package/ios/RNFBDatabase/RNFBDatabaseOnDisconnectHelper.m +29 -26
  48. package/ios/RNFBDatabase/RNFBDatabaseQuery.h +3 -2
  49. package/ios/RNFBDatabase/RNFBDatabaseQuery.m +14 -13
  50. package/ios/RNFBDatabase/RNFBDatabaseQueryHelper.h +7 -0
  51. package/ios/RNFBDatabase/RNFBDatabaseQueryHelper.m +44 -21
  52. package/ios/RNFBDatabase/RNFBDatabaseQueryModule.mm +14 -0
  53. package/ios/RNFBDatabase/RNFBDatabaseQueryRegistry.h +37 -0
  54. package/ios/RNFBDatabase/RNFBDatabaseQueryRegistry.m +79 -0
  55. package/ios/RNFBDatabase/RNFBDatabaseQueryRegistryStorage.swift +104 -0
  56. package/ios/RNFBDatabase/RNFBDatabaseReferenceHelper.m +14 -12
  57. package/ios/RNFBDatabase/RNFBDatabaseTransactionHelper.m +3 -1
  58. package/ios/RNFBDatabase.xcodeproj/project.pbxproj +12 -0
  59. package/ios/RNFBDatabaseUnitTests/RNFBDatabaseListenerRegistryStorageTests.swift +86 -0
  60. package/ios/RNFBDatabaseUnitTests/RNFBDatabaseListenerRegistryTests.m +90 -0
  61. package/ios/RNFBDatabaseUnitTests/RNFBDatabaseNullSentinelDecodeTests.m +118 -0
  62. package/ios/RNFBDatabaseUnitTests/RNFBDatabaseQueryRegistryStorageTests.swift +181 -0
  63. package/ios/RNFBDatabaseUnitTests/RNFBDatabaseQueryRegistryTests.m +390 -0
  64. package/ios/RNFBDatabaseUnitTests/RNFBDatabaseUnitTests.xcodeproj/project.pbxproj +303 -0
  65. package/ios/RNFBDatabaseUnitTests/RNFBDatabaseUnitTests.xcodeproj/xcshareddata/xcschemes/RNFBDatabaseUnitTests.xcscheme +69 -0
  66. package/ios/generated/RNFBDatabaseTurboModules/RNFBDatabaseTurboModules-generated.mm +7 -0
  67. package/ios/generated/RNFBDatabaseTurboModules/RNFBDatabaseTurboModules.h +6 -0
  68. package/ios/generated/RNFBDatabaseTurboModulesJSI.h +12 -0
  69. package/lib/DatabaseQuery.ts +56 -0
  70. package/lib/DatabaseSyncTree.ts +1 -1
  71. package/lib/index.ts +1 -1
  72. package/lib/modular/query.ts +1 -1
  73. package/lib/types/internal.ts +2 -0
  74. package/lib/version.ts +1 -1
  75. package/lib/web/RNFBDatabaseModule.ts +13 -0
  76. package/package.json +5 -4
  77. package/specs/NativeRNFBTurboDatabaseQuery.ts +1 -0
@@ -18,12 +18,12 @@ package io.invertase.firebase.database;
18
18
  */
19
19
 
20
20
  import static io.invertase.firebase.common.RCTConvertFirebase.toHashMap;
21
+ import static io.invertase.firebase.database.ReactNativeFirebaseDatabaseCommon.rejectPromiseDatabaseException;
21
22
 
22
23
  import com.facebook.fbreact.specs.NativeRNFBTurboDatabaseOnDisconnectSpec;
23
24
  import com.facebook.react.bridge.Promise;
24
25
  import com.facebook.react.bridge.ReactApplicationContext;
25
26
  import com.facebook.react.bridge.ReadableMap;
26
- import io.invertase.firebase.common.ReactNativeFirebaseModule;
27
27
  import java.util.Map;
28
28
 
29
29
  public class NativeRNFBTurboDatabaseOnDisconnect extends NativeRNFBTurboDatabaseOnDisconnectSpec {
@@ -44,8 +44,7 @@ public class NativeRNFBTurboDatabaseOnDisconnect extends NativeRNFBTurboDatabase
44
44
  if (task.isSuccessful()) {
45
45
  promise.resolve(task.getResult());
46
46
  } else {
47
- ReactNativeFirebaseModule.rejectPromiseWithExceptionMap(
48
- promise, task.getException());
47
+ rejectPromiseDatabaseException(promise, task.getException());
49
48
  }
50
49
  });
51
50
  }
@@ -59,8 +58,7 @@ public class NativeRNFBTurboDatabaseOnDisconnect extends NativeRNFBTurboDatabase
59
58
  if (task.isSuccessful()) {
60
59
  promise.resolve(task.getResult());
61
60
  } else {
62
- ReactNativeFirebaseModule.rejectPromiseWithExceptionMap(
63
- promise, task.getException());
61
+ rejectPromiseDatabaseException(promise, task.getException());
64
62
  }
65
63
  });
66
64
  }
@@ -75,8 +73,7 @@ public class NativeRNFBTurboDatabaseOnDisconnect extends NativeRNFBTurboDatabase
75
73
  if (task.isSuccessful()) {
76
74
  promise.resolve(task.getResult());
77
75
  } else {
78
- ReactNativeFirebaseModule.rejectPromiseWithExceptionMap(
79
- promise, task.getException());
76
+ rejectPromiseDatabaseException(promise, task.getException());
80
77
  }
81
78
  });
82
79
  }
@@ -92,8 +89,7 @@ public class NativeRNFBTurboDatabaseOnDisconnect extends NativeRNFBTurboDatabase
92
89
  if (task.isSuccessful()) {
93
90
  promise.resolve(task.getResult());
94
91
  } else {
95
- ReactNativeFirebaseModule.rejectPromiseWithExceptionMap(
96
- promise, task.getException());
92
+ rejectPromiseDatabaseException(promise, task.getException());
97
93
  }
98
94
  });
99
95
  }
@@ -110,8 +106,7 @@ public class NativeRNFBTurboDatabaseOnDisconnect extends NativeRNFBTurboDatabase
110
106
  if (task.isSuccessful()) {
111
107
  promise.resolve(task.getResult());
112
108
  } else {
113
- ReactNativeFirebaseModule.rejectPromiseWithExceptionMap(
114
- promise, task.getException());
109
+ rejectPromiseDatabaseException(promise, task.getException());
115
110
  }
116
111
  });
117
112
  }
@@ -26,11 +26,10 @@ import com.facebook.fbreact.specs.NativeRNFBTurboDatabaseQuerySpec;
26
26
  import com.facebook.react.bridge.*;
27
27
  import com.google.android.gms.tasks.Tasks;
28
28
  import com.google.firebase.database.*;
29
+ import io.invertase.firebase.common.RNFBHandleCollisionException;
29
30
  import io.invertase.firebase.common.ReactNativeFirebaseEventEmitter;
30
31
  import io.invertase.firebase.common.ReactNativeFirebaseModule;
31
- import java.util.HashMap;
32
- import java.util.Iterator;
33
- import java.util.Map;
32
+ import io.invertase.firebase.common.UniversalFirebasePreferences;
34
33
  import java.util.Objects;
35
34
  import javax.annotation.Nonnull;
36
35
  import javax.annotation.Nullable;
@@ -38,7 +37,7 @@ import javax.annotation.Nullable;
38
37
  public class NativeRNFBTurboDatabaseQuery extends NativeRNFBTurboDatabaseQuerySpec {
39
38
  private final DatabaseTurboModuleSupport turboSupport =
40
39
  new DatabaseTurboModuleSupport("RNFBDatabaseQuery");
41
- private HashMap<String, ReactNativeFirebaseDatabaseQuery> queryMap = new HashMap<>();
40
+ private final RNFBDatabaseQueryRegistry queryMap = new RNFBDatabaseQueryRegistry();
42
41
 
43
42
  public NativeRNFBTurboDatabaseQuery(ReactApplicationContext reactContext) {
44
43
  super(reactContext);
@@ -47,14 +46,7 @@ public class NativeRNFBTurboDatabaseQuery extends NativeRNFBTurboDatabaseQuerySp
47
46
  @Override
48
47
  @CallSuper
49
48
  public void invalidate() {
50
- Iterator refIterator = queryMap.entrySet().iterator();
51
- while (refIterator.hasNext()) {
52
- Map.Entry pair = (Map.Entry) refIterator.next();
53
- ReactNativeFirebaseDatabaseQuery databaseQuery =
54
- (ReactNativeFirebaseDatabaseQuery) pair.getValue();
55
- databaseQuery.removeAllEventListeners();
56
- refIterator.remove();
57
- }
49
+ queryMap.takeAllAndRemove();
58
50
 
59
51
  turboSupport.invalidate();
60
52
  super.invalidate();
@@ -67,16 +59,23 @@ public class NativeRNFBTurboDatabaseQuery extends NativeRNFBTurboDatabaseQuerySp
67
59
 
68
60
  private ReactNativeFirebaseDatabaseQuery getDatabaseQueryInstance(
69
61
  String key, DatabaseReference reference, ReadableArray modifiers) {
70
- ReactNativeFirebaseDatabaseQuery cachedDatabaseQuery = queryMap.get(key);
62
+ DatabaseQueryHandle cached = queryMap.get(key);
71
63
 
72
- if (cachedDatabaseQuery != null) {
73
- return cachedDatabaseQuery;
64
+ if (cached instanceof ReactNativeFirebaseDatabaseQuery) {
65
+ return (ReactNativeFirebaseDatabaseQuery) cached;
74
66
  }
75
67
 
76
68
  ReactNativeFirebaseDatabaseQuery databaseQuery =
77
69
  new ReactNativeFirebaseDatabaseQuery(reference, modifiers);
78
70
 
79
- queryMap.put(key, databaseQuery);
71
+ try {
72
+ queryMap.put(key, databaseQuery);
73
+ } catch (RNFBHandleCollisionException collision) {
74
+ DatabaseQueryHandle winner = queryMap.get(key);
75
+ if (winner instanceof ReactNativeFirebaseDatabaseQuery) {
76
+ return (ReactNativeFirebaseDatabaseQuery) winner;
77
+ }
78
+ }
80
79
  return databaseQuery;
81
80
  }
82
81
 
@@ -376,6 +375,88 @@ public class NativeRNFBTurboDatabaseQuery extends NativeRNFBTurboDatabaseQuerySp
376
375
  }
377
376
  }
378
377
 
378
+ // Static because the SDK keeps keepSynced registrations across a JS reload.
379
+ private static final RNFBDatabaseKeepSyncedRegistry keepSyncedRegistry =
380
+ new RNFBDatabaseKeepSyncedRegistry();
381
+
382
+ // reference.toString() is the full URL, so a null dbURL and the default URL give the same key.
383
+ private static String locationKey(String app, DatabaseReference reference) {
384
+ return app + "|" + reference;
385
+ }
386
+
387
+ private static final java.util.regex.Pattern PERMISSION_DENIED_REASON =
388
+ java.util.regex.Pattern.compile(
389
+ "permission.?denied", java.util.regex.Pattern.CASE_INSENSITIVE);
390
+
391
+ /**
392
+ * Single request/response read backing modular get(). Offline, Query.get() waits for the
393
+ * connection, like once(). It uses once() instead when:
394
+ *
395
+ * <ul>
396
+ * <li>disk persistence is on: Query.get() then resolves from disk after 3s, even online;
397
+ * <li>RTDB debug logging is on: a get in flight when the socket drops is resent on reconnect
398
+ * only because PersistentConnectionImpl.sendGet's early return sits inside {@code if
399
+ * (logger.logsDebug())};
400
+ * <li>the app keeps anything at this location synced (see RNFBDatabaseKeepSyncedRegistry).
401
+ * </ul>
402
+ */
403
+ @Override
404
+ public void get(String app, String dbURL, String path, ReadableArray modifiers, Promise promise) {
405
+ DatabaseReference reference = getDatabaseForApp(app, dbURL).getReference(path);
406
+ ReactNativeFirebaseDatabaseQuery databaseQuery = getDatabaseQueryInstance(reference, modifiers);
407
+
408
+ UniversalFirebasePreferences preferences = UniversalFirebasePreferences.getSharedInstance();
409
+ String location = locationKey(app, reference);
410
+ if (modifiers.size() > 0
411
+ || preferences.getBooleanValue(UniversalDatabaseStatics.DATABASE_PERSISTENCE_ENABLED, false)
412
+ || preferences.getBooleanValue(UniversalDatabaseStatics.DATABASE_LOGGING_ENABLED, false)
413
+ || !keepSyncedRegistry.beginNativeGet(location)) {
414
+ addOnceValueEventListener(databaseQuery, promise);
415
+ return;
416
+ }
417
+
418
+ databaseQuery
419
+ .query
420
+ .get()
421
+ .addOnCompleteListener(
422
+ getTask -> {
423
+ keepSyncedRegistry.endNativeGet(location);
424
+ if (!getTask.isSuccessful()) {
425
+ Exception exception = getTask.getException();
426
+ // Query.get() surfaces a server error as a plain Exception carrying only the
427
+ // reply's reason text. Map a denial to the error once() gives, so callers see the
428
+ // same code and DatabaseQuery._get need not re-ask the server.
429
+ if (exception != null
430
+ && exception.getMessage() != null
431
+ && PERMISSION_DENIED_REASON.matcher(exception.getMessage()).find()) {
432
+ rejectPromiseDatabaseException(
433
+ promise,
434
+ new UniversalDatabaseException(
435
+ DatabaseError.PERMISSION_DENIED, exception.getMessage(), exception));
436
+ return;
437
+ }
438
+ ReactNativeFirebaseModule.rejectPromiseWithExceptionMap(promise, exception);
439
+ return;
440
+ }
441
+ DataSnapshot dataSnapshot = getTask.getResult();
442
+ // Serialise off the main thread, as addOnceValueEventListener does.
443
+ try {
444
+ Tasks.call(turboSupport.getExecutor(), () -> snapshotToMap(dataSnapshot))
445
+ .addOnCompleteListener(
446
+ task -> {
447
+ if (task.isSuccessful()) {
448
+ promise.resolve(task.getResult());
449
+ } else {
450
+ ReactNativeFirebaseModule.rejectPromiseWithExceptionMap(
451
+ promise, task.getException());
452
+ }
453
+ });
454
+ } catch (java.util.concurrent.RejectedExecutionException e) {
455
+ ReactNativeFirebaseModule.rejectPromiseWithExceptionMap(promise, e);
456
+ }
457
+ });
458
+ }
459
+
379
460
  @Override
380
461
  public void on(String app, String dbURL, ReadableMap props) {
381
462
  String key = props.getString("key");
@@ -396,15 +477,13 @@ public class NativeRNFBTurboDatabaseQuery extends NativeRNFBTurboDatabaseQuerySp
396
477
 
397
478
  @Override
398
479
  public void off(String queryKey, String eventRegistrationKey) {
399
- ReactNativeFirebaseDatabaseQuery databaseQuery = queryMap.get(queryKey);
480
+ DatabaseQueryHandle handle = queryMap.get(queryKey);
400
481
 
401
- if (databaseQuery != null) {
482
+ if (handle instanceof ReactNativeFirebaseDatabaseQuery) {
483
+ ReactNativeFirebaseDatabaseQuery databaseQuery = (ReactNativeFirebaseDatabaseQuery) handle;
402
484
  databaseQuery.removeEventListener(eventRegistrationKey);
403
485
  turboSupport.removeEventListeningExecutor(eventRegistrationKey);
404
-
405
- if (!databaseQuery.hasListeners()) {
406
- queryMap.remove(queryKey);
407
- }
486
+ queryMap.takeIfIdle(queryKey);
408
487
  }
409
488
  }
410
489
 
@@ -418,7 +497,15 @@ public class NativeRNFBTurboDatabaseQuery extends NativeRNFBTurboDatabaseQuerySp
418
497
  boolean enabled,
419
498
  Promise promise) {
420
499
  DatabaseReference reference = getDatabaseForApp(app, dbURL).getReference(path);
421
- getDatabaseQueryInstance(key, reference, modifiers).query.keepSynced(enabled);
422
- promise.resolve(null);
500
+ Query query = getDatabaseQueryInstance(key, reference, modifiers).query;
501
+ // Resolve only after the SDK apply runs (now, or after endNativeGet when deferred).
502
+ keepSyncedRegistry.keepSynced(
503
+ locationKey(app, reference),
504
+ key,
505
+ enabled,
506
+ () -> {
507
+ query.keepSynced(enabled);
508
+ promise.resolve(null);
509
+ });
423
510
  }
424
511
  }
@@ -20,7 +20,6 @@ package io.invertase.firebase.database;
20
20
  import static io.invertase.firebase.database.UniversalFirebaseDatabaseCommon.getDatabaseForApp;
21
21
 
22
22
  import android.os.AsyncTask;
23
- import android.util.SparseArray;
24
23
  import com.facebook.fbreact.specs.NativeRNFBTurboDatabaseTransactionSpec;
25
24
  import com.facebook.react.bridge.ReactApplicationContext;
26
25
  import com.facebook.react.bridge.ReadableMap;
@@ -31,8 +30,8 @@ import javax.annotation.Nonnull;
31
30
 
32
31
  public class NativeRNFBTurboDatabaseTransaction extends NativeRNFBTurboDatabaseTransactionSpec {
33
32
  private static final String SERVICE_NAME = "DatabaseTransaction";
34
- private static SparseArray<ReactNativeFirebaseDatabaseTransactionHandler> transactionHandlers =
35
- new SparseArray<>();
33
+ private static final RNFBDatabaseTransactionRegistry transactionHandlers =
34
+ new RNFBDatabaseTransactionRegistry();
36
35
  private final DatabaseTurboModuleSupport turboSupport =
37
36
  new DatabaseTurboModuleSupport("RNFBDatabaseTransaction");
38
37
 
@@ -42,17 +41,7 @@ public class NativeRNFBTurboDatabaseTransaction extends NativeRNFBTurboDatabaseT
42
41
 
43
42
  @Override
44
43
  public void invalidate() {
45
- for (int i = 0, size = transactionHandlers.size(); i < size; i++) {
46
- int key = transactionHandlers.keyAt(i);
47
- ReactNativeFirebaseDatabaseTransactionHandler transactionHandler =
48
- transactionHandlers.get(key);
49
-
50
- if (transactionHandler != null) {
51
- transactionHandler.abort();
52
- }
53
- }
54
-
55
- transactionHandlers.clear();
44
+ transactionHandlers.takeAllAndAbort();
56
45
  turboSupport.invalidate();
57
46
  }
58
47
 
@@ -71,7 +60,7 @@ public class NativeRNFBTurboDatabaseTransaction extends NativeRNFBTurboDatabaseT
71
60
  final ReactNativeFirebaseDatabaseTransactionHandler transactionHandler =
72
61
  new ReactNativeFirebaseDatabaseTransactionHandler(
73
62
  (int) transactionId, app, dbURL);
74
- transactionHandlers.put((int) transactionId, transactionHandler);
63
+ transactionHandlers.registerReplacing((int) transactionId, transactionHandler);
75
64
  final WritableMap updatesMap = transactionHandler.createUpdateMap(mutableData);
76
65
 
77
66
  AsyncTask.execute(
@@ -109,14 +98,15 @@ public class NativeRNFBTurboDatabaseTransaction extends NativeRNFBTurboDatabaseT
109
98
  @Override
110
99
  public void onComplete(
111
100
  DatabaseError error, boolean committed, DataSnapshot snapshot) {
112
- ReactNativeFirebaseDatabaseTransactionHandler transactionHandler =
113
- transactionHandlers.get((int) transactionId);
101
+ DatabaseAbortable abortable = transactionHandlers.take((int) transactionId);
114
102
 
115
- if (transactionHandler == null) {
116
- transactionHandlers.delete((int) transactionId);
103
+ if (!(abortable instanceof ReactNativeFirebaseDatabaseTransactionHandler)) {
117
104
  return;
118
105
  }
119
106
 
107
+ ReactNativeFirebaseDatabaseTransactionHandler transactionHandler =
108
+ (ReactNativeFirebaseDatabaseTransactionHandler) abortable;
109
+
120
110
  WritableMap resultMap =
121
111
  transactionHandler.createResultMap(error, committed, snapshot);
122
112
 
@@ -129,8 +119,6 @@ public class NativeRNFBTurboDatabaseTransaction extends NativeRNFBTurboDatabaseT
129
119
  resultMap,
130
120
  app,
131
121
  (int) transactionId));
132
-
133
- transactionHandlers.delete((int) transactionId);
134
122
  }
135
123
  },
136
124
  applyLocally);
@@ -140,11 +128,10 @@ public class NativeRNFBTurboDatabaseTransaction extends NativeRNFBTurboDatabaseT
140
128
  @Override
141
129
  public void transactionTryCommit(
142
130
  String app, String dbURL, double transactionId, ReadableMap updates) {
143
- ReactNativeFirebaseDatabaseTransactionHandler handler =
144
- transactionHandlers.get((int) transactionId);
131
+ DatabaseAbortable abortable = transactionHandlers.get((int) transactionId);
145
132
 
146
- if (handler != null) {
147
- handler.signalUpdateReceived(updates);
133
+ if (abortable instanceof ReactNativeFirebaseDatabaseTransactionHandler) {
134
+ ((ReactNativeFirebaseDatabaseTransactionHandler) abortable).signalUpdateReceived(updates);
148
135
  }
149
136
  }
150
137
  }
@@ -25,15 +25,14 @@ import com.google.firebase.database.ChildEventListener;
25
25
  import com.google.firebase.database.DatabaseReference;
26
26
  import com.google.firebase.database.Query;
27
27
  import com.google.firebase.database.ValueEventListener;
28
- import java.util.HashMap;
29
- import java.util.Iterator;
28
+ import io.invertase.firebase.common.RNFBHandleCollisionException;
29
+ import java.util.List;
30
30
  import java.util.Map;
31
31
 
32
- public class ReactNativeFirebaseDatabaseQuery {
32
+ public class ReactNativeFirebaseDatabaseQuery implements DatabaseQueryHandle {
33
33
 
34
34
  public Query query;
35
- private HashMap<String, ChildEventListener> childEventListeners = new HashMap<>();
36
- private HashMap<String, ValueEventListener> valueEventListeners = new HashMap<>();
35
+ final RNFBDatabaseListenerRegistry listeners = new RNFBDatabaseListenerRegistry();
37
36
 
38
37
  ReactNativeFirebaseDatabaseQuery(DatabaseReference reference, ReadableArray modifiers) {
39
38
  this.query = reference;
@@ -78,7 +77,11 @@ public class ReactNativeFirebaseDatabaseQuery {
78
77
  * @param listener
79
78
  */
80
79
  public void addEventListener(String eventRegistrationKey, ValueEventListener listener) {
81
- valueEventListeners.put(eventRegistrationKey, listener);
80
+ try {
81
+ listeners.putValue(eventRegistrationKey, listener);
82
+ } catch (RNFBHandleCollisionException | NullPointerException collision) {
83
+ return;
84
+ }
82
85
  query.addValueEventListener(listener);
83
86
  }
84
87
 
@@ -89,7 +92,11 @@ public class ReactNativeFirebaseDatabaseQuery {
89
92
  * @param listener
90
93
  */
91
94
  public void addEventListener(String eventRegistrationKey, ChildEventListener listener) {
92
- childEventListeners.put(eventRegistrationKey, listener);
95
+ try {
96
+ listeners.putChild(eventRegistrationKey, listener);
97
+ } catch (RNFBHandleCollisionException | NullPointerException collision) {
98
+ return;
99
+ }
93
100
  query.addChildEventListener(listener);
94
101
  }
95
102
 
@@ -117,36 +124,26 @@ public class ReactNativeFirebaseDatabaseQuery {
117
124
  * @param eventRegistrationKey
118
125
  */
119
126
  public void removeEventListener(String eventRegistrationKey) {
120
- if (valueEventListeners.containsKey(eventRegistrationKey)) {
121
- query.removeEventListener(valueEventListeners.get(eventRegistrationKey));
122
- valueEventListeners.remove(eventRegistrationKey);
127
+ Object valueListener = listeners.takeValue(eventRegistrationKey);
128
+ if (valueListener instanceof ValueEventListener) {
129
+ query.removeEventListener((ValueEventListener) valueListener);
123
130
  }
124
131
 
125
- if (childEventListeners.containsKey(eventRegistrationKey)) {
126
- query.removeEventListener(childEventListeners.get(eventRegistrationKey));
127
- childEventListeners.remove(eventRegistrationKey);
132
+ Object childListener = listeners.takeChild(eventRegistrationKey);
133
+ if (childListener instanceof ChildEventListener) {
134
+ query.removeEventListener((ChildEventListener) childListener);
128
135
  }
129
136
  }
130
137
 
131
138
  /** Iterates over all current event listeners on the current query and removes each one */
139
+ @Override
132
140
  public void removeAllEventListeners() {
133
- if (hasListeners()) {
134
- Iterator valueIterator = valueEventListeners.entrySet().iterator();
135
-
136
- while (valueIterator.hasNext()) {
137
- Map.Entry pair = (Map.Entry) valueIterator.next();
138
- ValueEventListener valueEventListener = (ValueEventListener) pair.getValue();
139
- query.removeEventListener(valueEventListener);
140
- valueIterator.remove();
141
- }
142
-
143
- Iterator childIterator = childEventListeners.entrySet().iterator();
144
-
145
- while (childIterator.hasNext()) {
146
- Map.Entry pair = (Map.Entry) childIterator.next();
147
- ChildEventListener childEventListener = (ChildEventListener) pair.getValue();
148
- query.removeEventListener(childEventListener);
149
- childIterator.remove();
141
+ List<Object> remaining = listeners.takeAll();
142
+ for (Object listener : remaining) {
143
+ if (listener instanceof ValueEventListener) {
144
+ query.removeEventListener((ValueEventListener) listener);
145
+ } else if (listener instanceof ChildEventListener) {
146
+ query.removeEventListener((ChildEventListener) listener);
150
147
  }
151
148
  }
152
149
  }
@@ -158,8 +155,7 @@ public class ReactNativeFirebaseDatabaseQuery {
158
155
  * @return
159
156
  */
160
157
  public Boolean hasEventListener(String eventRegistrationKey) {
161
- return valueEventListeners.containsKey(eventRegistrationKey)
162
- || childEventListeners.containsKey(eventRegistrationKey);
158
+ return listeners.hasEventListener(eventRegistrationKey);
163
159
  }
164
160
 
165
161
  /**
@@ -167,8 +163,9 @@ public class ReactNativeFirebaseDatabaseQuery {
167
163
  *
168
164
  * @return
169
165
  */
166
+ @Override
170
167
  public Boolean hasListeners() {
171
- return valueEventListeners.size() > 0 || childEventListeners.size() > 0;
168
+ return listeners.hasListeners();
172
169
  }
173
170
 
174
171
  /**
@@ -32,7 +32,7 @@ import java.util.concurrent.locks.Condition;
32
32
  import java.util.concurrent.locks.ReentrantLock;
33
33
  import javax.annotation.Nullable;
34
34
 
35
- public class ReactNativeFirebaseDatabaseTransactionHandler {
35
+ public class ReactNativeFirebaseDatabaseTransactionHandler implements DatabaseAbortable {
36
36
  private final ReentrantLock lock;
37
37
  private final Condition condition;
38
38
  public Object value;
@@ -81,8 +81,8 @@ public class ReactNativeFirebaseDatabaseTransactionHandler {
81
81
  }
82
82
  }
83
83
 
84
- /** Abort the currently in progress transaction if any. */
85
- void abort() {
84
+ @Override
85
+ public void abort() {
86
86
  lock.lock();
87
87
  try {
88
88
  abort = true;
@@ -38,6 +38,10 @@ public abstract class NativeRNFBTurboDatabaseQuerySpec extends ReactContextBaseJ
38
38
  @DoNotStrip
39
39
  public abstract void once(String app, String dbURL, String path, ReadableArray modifiers, String eventType, Promise promise);
40
40
 
41
+ @ReactMethod
42
+ @DoNotStrip
43
+ public abstract void get(String app, String dbURL, String path, ReadableArray modifiers, Promise promise);
44
+
41
45
  @ReactMethod
42
46
  @DoNotStrip
43
47
  public abstract void on(String app, String dbURL, ReadableMap props);
@@ -89,6 +89,11 @@ static facebook::jsi::Value __hostFunction_NativeRNFBTurboDatabaseQuerySpecJSI_o
89
89
  return static_cast<JavaTurboModule &>(turboModule).invokeJavaMethod(rt, PromiseKind, "once", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/facebook/react/bridge/ReadableArray;Ljava/lang/String;Lcom/facebook/react/bridge/Promise;)V", args, count, cachedMethodId);
90
90
  }
91
91
 
92
+ static facebook::jsi::Value __hostFunction_NativeRNFBTurboDatabaseQuerySpecJSI_get(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
93
+ static jmethodID cachedMethodId = nullptr;
94
+ return static_cast<JavaTurboModule &>(turboModule).invokeJavaMethod(rt, PromiseKind, "get", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/facebook/react/bridge/ReadableArray;Lcom/facebook/react/bridge/Promise;)V", args, count, cachedMethodId);
95
+ }
96
+
92
97
  static facebook::jsi::Value __hostFunction_NativeRNFBTurboDatabaseQuerySpecJSI_on(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
93
98
  static jmethodID cachedMethodId = nullptr;
94
99
  return static_cast<JavaTurboModule &>(turboModule).invokeJavaMethod(rt, VoidKind, "on", "(Ljava/lang/String;Ljava/lang/String;Lcom/facebook/react/bridge/ReadableMap;)V", args, count, cachedMethodId);
@@ -107,6 +112,7 @@ static facebook::jsi::Value __hostFunction_NativeRNFBTurboDatabaseQuerySpecJSI_k
107
112
  NativeRNFBTurboDatabaseQuerySpecJSI::NativeRNFBTurboDatabaseQuerySpecJSI(const JavaTurboModule::InitParams &params)
108
113
  : JavaTurboModule(params) {
109
114
  methodMap_["once"] = MethodMetadata {5, __hostFunction_NativeRNFBTurboDatabaseQuerySpecJSI_once};
115
+ methodMap_["get"] = MethodMetadata {4, __hostFunction_NativeRNFBTurboDatabaseQuerySpecJSI_get};
110
116
  methodMap_["on"] = MethodMetadata {3, __hostFunction_NativeRNFBTurboDatabaseQuerySpecJSI_on};
111
117
  methodMap_["off"] = MethodMetadata {2, __hostFunction_NativeRNFBTurboDatabaseQuerySpecJSI_off};
112
118
  methodMap_["keepSynced"] = MethodMetadata {6, __hostFunction_NativeRNFBTurboDatabaseQuerySpecJSI_keepSynced};
@@ -170,6 +170,7 @@ public:
170
170
  protected:
171
171
  NativeRNFBTurboDatabaseQueryCxxSpec(std::shared_ptr<CallInvoker> jsInvoker) : TurboModule(std::string{NativeRNFBTurboDatabaseQueryCxxSpec::kModuleName}, jsInvoker) {
172
172
  methodMap_["once"] = MethodMetadata {.argCount = 5, .invoker = __once};
173
+ methodMap_["get"] = MethodMetadata {.argCount = 4, .invoker = __get};
173
174
  methodMap_["on"] = MethodMetadata {.argCount = 3, .invoker = __on};
174
175
  methodMap_["off"] = MethodMetadata {.argCount = 2, .invoker = __off};
175
176
  methodMap_["keepSynced"] = MethodMetadata {.argCount = 6, .invoker = __keepSynced};
@@ -188,6 +189,17 @@ private:
188
189
  count <= 4 ? throw jsi::JSError(rt, "Expected argument in position 4 to be passed") : args[4].asString(rt));
189
190
  }
190
191
 
192
+ static jsi::Value __get(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) {
193
+ static_assert(
194
+ bridging::getParameterCount(&T::get) == 5,
195
+ "Expected get(...) to have 5 parameters");
196
+ return bridging::callFromJs<jsi::Value>(rt, &T::get, static_cast<NativeRNFBTurboDatabaseQueryCxxSpec*>(&turboModule)->jsInvoker_, static_cast<T*>(&turboModule),
197
+ count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asString(rt),
198
+ count <= 1 ? throw jsi::JSError(rt, "Expected argument in position 1 to be passed") : args[1].asString(rt),
199
+ count <= 2 ? throw jsi::JSError(rt, "Expected argument in position 2 to be passed") : args[2].asString(rt),
200
+ count <= 3 ? throw jsi::JSError(rt, "Expected argument in position 3 to be passed") : args[3].asObject(rt).asArray(rt));
201
+ }
202
+
191
203
  static jsi::Value __on(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) {
192
204
  static_assert(
193
205
  bridging::getParameterCount(&T::on) == 4,