@react-native-firebase/database 26.3.2 → 26.4.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 (31) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/RNFBDatabase.podspec +3 -1
  3. package/android/build.gradle +2 -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/RNFBDatabaseListenerRegistry.java +117 -0
  7. package/android/src/main/java/io/invertase/firebase/database/RNFBDatabaseQueryRegistry.java +78 -0
  8. package/android/src/main/java/io/invertase/firebase/database/RNFBDatabaseTransactionRegistry.java +71 -0
  9. package/android/src/reactnative/java/io/invertase/firebase/database/NativeRNFBTurboDatabaseQuery.java +18 -22
  10. package/android/src/reactnative/java/io/invertase/firebase/database/NativeRNFBTurboDatabaseTransaction.java +12 -25
  11. package/android/src/reactnative/java/io/invertase/firebase/database/ReactNativeFirebaseDatabaseQuery.java +30 -33
  12. package/android/src/reactnative/java/io/invertase/firebase/database/ReactNativeFirebaseDatabaseTransactionHandler.java +3 -3
  13. package/android/src/test/java/io/invertase/firebase/database/RNFBDatabaseListenerRegistryTest.java +160 -0
  14. package/android/src/test/java/io/invertase/firebase/database/RNFBDatabaseQueryRegistryTest.java +434 -0
  15. package/android/src/test/java/io/invertase/firebase/database/RNFBDatabaseTransactionRegistryTest.java +199 -0
  16. package/dist/module/version.js +1 -1
  17. package/dist/typescript/lib/index.d.ts +1 -1
  18. package/dist/typescript/lib/version.d.ts +1 -1
  19. package/ios/RNFBDatabase/RNFBDatabaseListenerRegistry.h +37 -0
  20. package/ios/RNFBDatabase/RNFBDatabaseListenerRegistry.m +84 -0
  21. package/ios/RNFBDatabase/RNFBDatabaseQuery.h +3 -2
  22. package/ios/RNFBDatabase/RNFBDatabaseQuery.m +14 -13
  23. package/ios/RNFBDatabase/RNFBDatabaseQueryHelper.m +24 -21
  24. package/ios/RNFBDatabase/RNFBDatabaseQueryRegistry.h +37 -0
  25. package/ios/RNFBDatabase/RNFBDatabaseQueryRegistry.m +105 -0
  26. package/ios/RNFBDatabaseUnitTests/RNFBDatabaseListenerRegistryTests.m +90 -0
  27. package/ios/RNFBDatabaseUnitTests/RNFBDatabaseQueryRegistryTests.m +390 -0
  28. package/ios/RNFBDatabaseUnitTests/RNFBDatabaseUnitTests.xcodeproj/project.pbxproj +261 -0
  29. package/ios/RNFBDatabaseUnitTests/RNFBDatabaseUnitTests.xcodeproj/xcshareddata/xcschemes/RNFBDatabaseUnitTests.xcscheme +69 -0
  30. package/lib/version.ts +1 -1
  31. package/package.json +5 -4
package/CHANGELOG.md CHANGED
@@ -3,6 +3,18 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [26.4.0](https://github.com/invertase/react-native-firebase/compare/v26.3.3...v26.4.0) (2026-09-05)
7
+
8
+ ### Bug Fixes
9
+
10
+ - **database:** use RNFBHandleMap for transaction and query maps ([8adba4c](https://github.com/invertase/react-native-firebase/commit/8adba4cb88dffec2442ea0fe37738d1080d0a185))
11
+
12
+ ## [26.3.3](https://github.com/invertase/react-native-firebase/compare/v26.3.2...v26.3.3) (2026-09-01)
13
+
14
+ ### Reverts
15
+
16
+ - Revert "chore(release): release packages" ([0a85268](https://github.com/invertase/react-native-firebase/commit/0a85268ae21c706cf1392eaa5b088e85c3273cb7))
17
+
6
18
  ## [26.3.2](https://github.com/invertase/react-native-firebase/compare/v26.3.1...v26.3.2) (2026-08-21)
7
19
 
8
20
  **Note:** Version bump only for package @react-native-firebase/database
@@ -36,9 +36,11 @@ Pod::Spec.new do |s|
36
36
  'ios/RNFBDatabase/RNFBDatabaseQueryModule.h',
37
37
  'ios/RNFBDatabase/RNFBDatabaseOnDisconnectModule.h',
38
38
  'ios/RNFBDatabase/RNFBDatabaseTransactionModule.h',
39
+ 'ios/RNFBDatabase/RNFBDatabaseQueryRegistry.h',
40
+ 'ios/RNFBDatabase/RNFBDatabaseListenerRegistry.h',
39
41
  'ios/generated/**/*.h',
40
42
  ]
41
- s.exclude_files = 'ios/generated/RCTThirdPartyComponentsProvider.*', 'ios/generated/RCTAppDependencyProvider.*', 'ios/generated/RCTModuleProviders.*', 'ios/generated/RCTModulesConformingToProtocolsProvider.*', 'ios/generated/RCTUnstableModulesRequiringMainQueueSetupProvider.*'
43
+ s.exclude_files = 'ios/generated/RCTThirdPartyComponentsProvider.*', 'ios/generated/RCTAppDependencyProvider.*', 'ios/generated/RCTModuleProviders.*', 'ios/generated/RCTModulesConformingToProtocolsProvider.*', 'ios/generated/RCTUnstableModulesRequiringMainQueueSetupProvider.*', 'ios/*UnitTests/**'
42
44
 
43
45
  s.pod_target_xcconfig = {
44
46
  "HEADER_SEARCH_PATHS" => "\"$(PODS_TARGET_SRCROOT)/ios/generated/RNFBDatabaseTurboModules\" \"$(PODS_TARGET_SRCROOT)/ios/generated\"",
@@ -93,6 +93,8 @@ dependencies {
93
93
  api appProject
94
94
  implementation platform("com.google.firebase:firebase-bom:${ReactNative.ext.getVersion("firebase", "bom")}")
95
95
  implementation "com.google.firebase:firebase-database"
96
+
97
+ testImplementation "junit:junit:4.13.2"
96
98
  }
97
99
 
98
100
  ReactNative.shared.applyPackageVersion()
@@ -0,0 +1,22 @@
1
+ package io.invertase.firebase.database;
2
+
3
+ /*
4
+ * Copyright (c) 2016-present Invertase Limited & Contributors
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this library except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ *
18
+ */
19
+
20
+ interface DatabaseAbortable {
21
+ void abort();
22
+ }
@@ -0,0 +1,24 @@
1
+ package io.invertase.firebase.database;
2
+
3
+ /*
4
+ * Copyright (c) 2016-present Invertase Limited & Contributors
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this library except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ *
18
+ */
19
+
20
+ interface DatabaseQueryHandle {
21
+ void removeAllEventListeners();
22
+
23
+ Boolean hasListeners();
24
+ }
@@ -0,0 +1,117 @@
1
+ package io.invertase.firebase.database;
2
+
3
+ /*
4
+ * Copyright (c) 2016-present Invertase Limited & Contributors
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this library except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ *
18
+ */
19
+
20
+ import io.invertase.firebase.common.RNFBHandleCollisionException;
21
+ import io.invertase.firebase.common.RNFBHandleMap;
22
+ import java.util.ArrayList;
23
+ import java.util.List;
24
+
25
+ /**
26
+ * Per-query value/child listener maps. Unique {@link #putValue}/{@link #putChild}; callers {@link
27
+ * #takeValue}/{@link #takeChild} or {@link #takeAll} then remove the SDK listener outside the
28
+ * HandleMap lock. Null values are not stored so {@link #hasListeners} can use occupancy.
29
+ */
30
+ final class RNFBDatabaseListenerRegistry {
31
+ private final Object occupancyLock = new Object();
32
+ private final RNFBHandleMap<String, Object> valueListeners = new RNFBHandleMap<>();
33
+ private final RNFBHandleMap<String, Object> childListeners = new RNFBHandleMap<>();
34
+ private int occupancy;
35
+
36
+ void putValue(String eventRegistrationKey, Object listener) throws RNFBHandleCollisionException {
37
+ if (listener == null) {
38
+ throw new NullPointerException("listener");
39
+ }
40
+ synchronized (occupancyLock) {
41
+ valueListeners.put(eventRegistrationKey, listener);
42
+ occupancy++;
43
+ }
44
+ }
45
+
46
+ void putChild(String eventRegistrationKey, Object listener) throws RNFBHandleCollisionException {
47
+ if (listener == null) {
48
+ throw new NullPointerException("listener");
49
+ }
50
+ synchronized (occupancyLock) {
51
+ childListeners.put(eventRegistrationKey, listener);
52
+ occupancy++;
53
+ }
54
+ }
55
+
56
+ Object getValue(String eventRegistrationKey) {
57
+ return valueListeners.get(eventRegistrationKey);
58
+ }
59
+
60
+ Object getChild(String eventRegistrationKey) {
61
+ return childListeners.get(eventRegistrationKey);
62
+ }
63
+
64
+ Object takeValue(String eventRegistrationKey) {
65
+ synchronized (occupancyLock) {
66
+ Object listener = valueListeners.take(eventRegistrationKey);
67
+ if (listener != null) {
68
+ occupancy--;
69
+ }
70
+ return listener;
71
+ }
72
+ }
73
+
74
+ Object takeChild(String eventRegistrationKey) {
75
+ synchronized (occupancyLock) {
76
+ Object listener = childListeners.take(eventRegistrationKey);
77
+ if (listener != null) {
78
+ occupancy--;
79
+ }
80
+ return listener;
81
+ }
82
+ }
83
+
84
+ List<Object> takeAllValues() {
85
+ synchronized (occupancyLock) {
86
+ List<Object> remaining = valueListeners.takeAll();
87
+ occupancy -= remaining.size();
88
+ return remaining;
89
+ }
90
+ }
91
+
92
+ List<Object> takeAllChildren() {
93
+ synchronized (occupancyLock) {
94
+ List<Object> remaining = childListeners.takeAll();
95
+ occupancy -= remaining.size();
96
+ return remaining;
97
+ }
98
+ }
99
+
100
+ List<Object> takeAll() {
101
+ List<Object> remaining = new ArrayList<>();
102
+ remaining.addAll(takeAllValues());
103
+ remaining.addAll(takeAllChildren());
104
+ return remaining;
105
+ }
106
+
107
+ boolean hasEventListener(String eventRegistrationKey) {
108
+ return valueListeners.get(eventRegistrationKey) != null
109
+ || childListeners.get(eventRegistrationKey) != null;
110
+ }
111
+
112
+ boolean hasListeners() {
113
+ synchronized (occupancyLock) {
114
+ return occupancy > 0;
115
+ }
116
+ }
117
+ }
@@ -0,0 +1,78 @@
1
+ package io.invertase.firebase.database;
2
+
3
+ /*
4
+ * Copyright (c) 2016-present Invertase Limited & Contributors
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this library except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ *
18
+ */
19
+
20
+ import io.invertase.firebase.common.RNFBHandleCollisionException;
21
+ import io.invertase.firebase.common.RNFBHandleMap;
22
+ import java.util.List;
23
+
24
+ /**
25
+ * Cached query map. Unique {@link #put}; callers {@link #take}, {@link #takeIfIdle}, or {@link
26
+ * #takeAllAndRemove} then {@code removeAllEventListeners()} outside the HandleMap lock.
27
+ */
28
+ final class RNFBDatabaseQueryRegistry {
29
+ private final RNFBHandleMap<String, DatabaseQueryHandle> map = new RNFBHandleMap<>();
30
+
31
+ void put(String queryKey, DatabaseQueryHandle query) throws RNFBHandleCollisionException {
32
+ map.put(queryKey, query);
33
+ }
34
+
35
+ DatabaseQueryHandle get(String queryKey) {
36
+ return map.get(queryKey);
37
+ }
38
+
39
+ DatabaseQueryHandle take(String queryKey) {
40
+ return map.take(queryKey);
41
+ }
42
+
43
+ /**
44
+ * Removes the mapping when the query has no listeners. Reads {@link
45
+ * DatabaseQueryHandle#hasListeners()} outside the HandleMap lock (avoids nesting {@code
46
+ * occupancyLock}), then identity-takes and put-backs if listeners appeared.
47
+ */
48
+ void takeIfIdle(String queryKey) {
49
+ DatabaseQueryHandle query = map.get(queryKey);
50
+ if (query == null) {
51
+ return;
52
+ }
53
+ if (Boolean.TRUE.equals(query.hasListeners())) {
54
+ return;
55
+ }
56
+ DatabaseQueryHandle taken = map.takeIf(queryKey, q -> q == query);
57
+ if (taken == null) {
58
+ return;
59
+ }
60
+ if (Boolean.TRUE.equals(taken.hasListeners())) {
61
+ // Prefer put-back so an active query stays registered. If a concurrent put claimed the
62
+ // slot, putIfAbsent fails and this taken query would be an orphan with listeners — clear
63
+ // them so SDK callbacks are not left attached outside the registry.
64
+ if (!map.putIfAbsent(queryKey, taken)) {
65
+ taken.removeAllEventListeners();
66
+ }
67
+ }
68
+ }
69
+
70
+ void takeAllAndRemove() {
71
+ List<DatabaseQueryHandle> remaining = map.takeAll();
72
+ for (DatabaseQueryHandle query : remaining) {
73
+ if (query != null) {
74
+ query.removeAllEventListeners();
75
+ }
76
+ }
77
+ }
78
+ }
@@ -0,0 +1,71 @@
1
+ package io.invertase.firebase.database;
2
+
3
+ /*
4
+ * Copyright (c) 2016-present Invertase Limited & Contributors
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this library except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ *
18
+ */
19
+
20
+ import io.invertase.firebase.common.RNFBHandleCollisionException;
21
+ import io.invertase.firebase.common.RNFBHandleMap;
22
+ import java.util.List;
23
+
24
+ /**
25
+ * Transaction handler map. Unique {@link #put} (occupied id aborts the incoming handler, then
26
+ * throws); callers {@link #take} or {@link #takeAllAndAbort} then {@code abort()} outside the
27
+ * HandleMap lock. Replace is {@link #registerReplacing} (atomic last wins).
28
+ */
29
+ final class RNFBDatabaseTransactionRegistry {
30
+ private final RNFBHandleMap<Integer, DatabaseAbortable> map = new RNFBHandleMap<>();
31
+
32
+ void put(int transactionId, DatabaseAbortable handler) throws RNFBHandleCollisionException {
33
+ try {
34
+ map.put(transactionId, handler);
35
+ } catch (RNFBHandleCollisionException collision) {
36
+ if (handler != null) {
37
+ handler.abort();
38
+ }
39
+ throw collision;
40
+ }
41
+ }
42
+
43
+ /** Atomic replace (last wins). The replaced handler is not aborted (Firebase retry). */
44
+ void registerReplacing(int transactionId, DatabaseAbortable handler) {
45
+ map.putReplacing(transactionId, handler);
46
+ }
47
+
48
+ DatabaseAbortable get(int transactionId) {
49
+ return map.get(transactionId);
50
+ }
51
+
52
+ DatabaseAbortable take(int transactionId) {
53
+ return map.take(transactionId);
54
+ }
55
+
56
+ void takeAndAbort(int transactionId) {
57
+ DatabaseAbortable handler = map.take(transactionId);
58
+ if (handler != null) {
59
+ handler.abort();
60
+ }
61
+ }
62
+
63
+ void takeAllAndAbort() {
64
+ List<DatabaseAbortable> remaining = map.takeAll();
65
+ for (DatabaseAbortable handler : remaining) {
66
+ if (handler != null) {
67
+ handler.abort();
68
+ }
69
+ }
70
+ }
71
+ }
@@ -26,11 +26,9 @@ 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;
34
32
  import java.util.Objects;
35
33
  import javax.annotation.Nonnull;
36
34
  import javax.annotation.Nullable;
@@ -38,7 +36,7 @@ import javax.annotation.Nullable;
38
36
  public class NativeRNFBTurboDatabaseQuery extends NativeRNFBTurboDatabaseQuerySpec {
39
37
  private final DatabaseTurboModuleSupport turboSupport =
40
38
  new DatabaseTurboModuleSupport("RNFBDatabaseQuery");
41
- private HashMap<String, ReactNativeFirebaseDatabaseQuery> queryMap = new HashMap<>();
39
+ private final RNFBDatabaseQueryRegistry queryMap = new RNFBDatabaseQueryRegistry();
42
40
 
43
41
  public NativeRNFBTurboDatabaseQuery(ReactApplicationContext reactContext) {
44
42
  super(reactContext);
@@ -47,14 +45,7 @@ public class NativeRNFBTurboDatabaseQuery extends NativeRNFBTurboDatabaseQuerySp
47
45
  @Override
48
46
  @CallSuper
49
47
  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
- }
48
+ queryMap.takeAllAndRemove();
58
49
 
59
50
  turboSupport.invalidate();
60
51
  super.invalidate();
@@ -67,16 +58,23 @@ public class NativeRNFBTurboDatabaseQuery extends NativeRNFBTurboDatabaseQuerySp
67
58
 
68
59
  private ReactNativeFirebaseDatabaseQuery getDatabaseQueryInstance(
69
60
  String key, DatabaseReference reference, ReadableArray modifiers) {
70
- ReactNativeFirebaseDatabaseQuery cachedDatabaseQuery = queryMap.get(key);
61
+ DatabaseQueryHandle cached = queryMap.get(key);
71
62
 
72
- if (cachedDatabaseQuery != null) {
73
- return cachedDatabaseQuery;
63
+ if (cached instanceof ReactNativeFirebaseDatabaseQuery) {
64
+ return (ReactNativeFirebaseDatabaseQuery) cached;
74
65
  }
75
66
 
76
67
  ReactNativeFirebaseDatabaseQuery databaseQuery =
77
68
  new ReactNativeFirebaseDatabaseQuery(reference, modifiers);
78
69
 
79
- queryMap.put(key, databaseQuery);
70
+ try {
71
+ queryMap.put(key, databaseQuery);
72
+ } catch (RNFBHandleCollisionException collision) {
73
+ DatabaseQueryHandle winner = queryMap.get(key);
74
+ if (winner instanceof ReactNativeFirebaseDatabaseQuery) {
75
+ return (ReactNativeFirebaseDatabaseQuery) winner;
76
+ }
77
+ }
80
78
  return databaseQuery;
81
79
  }
82
80
 
@@ -396,15 +394,13 @@ public class NativeRNFBTurboDatabaseQuery extends NativeRNFBTurboDatabaseQuerySp
396
394
 
397
395
  @Override
398
396
  public void off(String queryKey, String eventRegistrationKey) {
399
- ReactNativeFirebaseDatabaseQuery databaseQuery = queryMap.get(queryKey);
397
+ DatabaseQueryHandle handle = queryMap.get(queryKey);
400
398
 
401
- if (databaseQuery != null) {
399
+ if (handle instanceof ReactNativeFirebaseDatabaseQuery) {
400
+ ReactNativeFirebaseDatabaseQuery databaseQuery = (ReactNativeFirebaseDatabaseQuery) handle;
402
401
  databaseQuery.removeEventListener(eventRegistrationKey);
403
402
  turboSupport.removeEventListeningExecutor(eventRegistrationKey);
404
-
405
- if (!databaseQuery.hasListeners()) {
406
- queryMap.remove(queryKey);
407
- }
403
+ queryMap.takeIfIdle(queryKey);
408
404
  }
409
405
  }
410
406
 
@@ -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;