@react-native-firebase/database 26.4.0 → 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.
- package/CHANGELOG.md +21 -0
- package/RNFBDatabase.podspec +8 -2
- package/android/build.gradle +5 -0
- package/android/src/main/java/io/invertase/firebase/database/RNFBDatabaseKeepSyncedRegistry.java +106 -0
- package/android/src/main/java/io/invertase/firebase/database/RNFBDatabaseListenerRegistry.kt +133 -0
- package/android/src/main/java/io/invertase/firebase/database/RNFBDatabaseQueryRegistry.kt +76 -0
- package/android/src/main/java/io/invertase/firebase/database/RNFBDatabaseTransactionRegistry.kt +67 -0
- package/android/src/main/java/io/invertase/firebase/database/UniversalFirebaseDatabaseOnDisconnectModule.java +102 -9
- package/android/src/reactnative/java/io/invertase/firebase/database/NativeRNFBTurboDatabaseOnDisconnect.java +6 -11
- package/android/src/reactnative/java/io/invertase/firebase/database/NativeRNFBTurboDatabaseQuery.java +93 -2
- package/android/src/reactnative/java/io/invertase/firebase/database/generated/java/com/facebook/fbreact/specs/NativeRNFBTurboDatabaseQuerySpec.java +4 -0
- package/android/src/reactnative/java/io/invertase/firebase/database/generated/jni/RNFBDatabaseTurboModules-generated.cpp +6 -0
- package/android/src/reactnative/java/io/invertase/firebase/database/generated/jni/react/renderer/components/RNFBDatabaseTurboModules/RNFBDatabaseTurboModulesJSI.h +12 -0
- package/android/src/test/java/io/invertase/firebase/database/RNFBDatabaseKeepSyncedRegistryTest.java +174 -0
- package/android/src/test/java/io/invertase/firebase/database/RNFBDatabaseListenerRegistryTest.java +31 -0
- package/android/src/test/java/io/invertase/firebase/database/RNFBDatabaseQueryRegistryTest.java +55 -0
- package/android/src/test/java/io/invertase/firebase/database/UniversalFirebaseDatabaseOnDisconnectCancelErrorTest.java +137 -0
- package/dist/module/DatabaseQuery.js +52 -1
- package/dist/module/DatabaseQuery.js.map +1 -1
- package/dist/module/DatabaseSyncTree.js +1 -1
- package/dist/module/DatabaseSyncTree.js.map +1 -1
- package/dist/module/index.js +1 -2
- package/dist/module/index.js.map +1 -1
- package/dist/module/modular/query.js +1 -1
- package/dist/module/modular/query.js.map +1 -1
- package/dist/module/version.js +1 -1
- package/dist/module/web/RNFBDatabaseModule.js +7 -0
- package/dist/module/web/RNFBDatabaseModule.js.map +1 -1
- package/dist/typescript/lib/DatabaseQuery.d.ts +25 -0
- package/dist/typescript/lib/DatabaseQuery.d.ts.map +1 -1
- package/dist/typescript/lib/index.d.ts +1 -1
- package/dist/typescript/lib/types/internal.d.ts +2 -0
- package/dist/typescript/lib/types/internal.d.ts.map +1 -1
- package/dist/typescript/lib/version.d.ts +1 -1
- package/dist/typescript/lib/web/RNFBDatabaseModule.d.ts.map +1 -1
- package/ios/RNFBDatabase/RNFBDatabaseListenerRegistry.m +24 -17
- package/ios/RNFBDatabase/RNFBDatabaseListenerRegistryStorage.swift +81 -0
- package/ios/RNFBDatabase/RNFBDatabaseNullSentinelDecoder.h +36 -0
- package/ios/RNFBDatabase/RNFBDatabaseNullSentinelDecoder.m +48 -0
- package/ios/RNFBDatabase/RNFBDatabaseOnDisconnectHelper.m +29 -26
- package/ios/RNFBDatabase/RNFBDatabaseQueryHelper.h +7 -0
- package/ios/RNFBDatabase/RNFBDatabaseQueryHelper.m +20 -0
- package/ios/RNFBDatabase/RNFBDatabaseQueryModule.mm +14 -0
- package/ios/RNFBDatabase/RNFBDatabaseQueryRegistry.m +25 -51
- package/ios/RNFBDatabase/RNFBDatabaseQueryRegistryStorage.swift +104 -0
- package/ios/RNFBDatabase/RNFBDatabaseReferenceHelper.m +14 -12
- package/ios/RNFBDatabase/RNFBDatabaseTransactionHelper.m +3 -1
- package/ios/RNFBDatabase.xcodeproj/project.pbxproj +12 -0
- package/ios/RNFBDatabaseUnitTests/RNFBDatabaseListenerRegistryStorageTests.swift +86 -0
- package/ios/RNFBDatabaseUnitTests/RNFBDatabaseNullSentinelDecodeTests.m +118 -0
- package/ios/RNFBDatabaseUnitTests/RNFBDatabaseQueryRegistryStorageTests.swift +181 -0
- package/ios/RNFBDatabaseUnitTests/RNFBDatabaseUnitTests.xcodeproj/project.pbxproj +42 -0
- package/ios/generated/RNFBDatabaseTurboModules/RNFBDatabaseTurboModules-generated.mm +7 -0
- package/ios/generated/RNFBDatabaseTurboModules/RNFBDatabaseTurboModules.h +6 -0
- package/ios/generated/RNFBDatabaseTurboModulesJSI.h +12 -0
- package/lib/DatabaseQuery.ts +56 -0
- package/lib/DatabaseSyncTree.ts +1 -1
- package/lib/index.ts +1 -1
- package/lib/modular/query.ts +1 -1
- package/lib/types/internal.ts +2 -0
- package/lib/version.ts +1 -1
- package/lib/web/RNFBDatabaseModule.ts +13 -0
- package/package.json +4 -4
- package/specs/NativeRNFBTurboDatabaseQuery.ts +1 -0
- package/android/src/main/java/io/invertase/firebase/database/RNFBDatabaseListenerRegistry.java +0 -117
- package/android/src/main/java/io/invertase/firebase/database/RNFBDatabaseQueryRegistry.java +0 -78
- package/android/src/main/java/io/invertase/firebase/database/RNFBDatabaseTransactionRegistry.java +0 -71
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,27 @@
|
|
|
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
|
+
## [27.0.0](https://github.com/invertase/react-native-firebase/compare/v26.4.0...v27.0.0) (2026-10-19)
|
|
7
|
+
|
|
8
|
+
### ⚠ BREAKING CHANGES
|
|
9
|
+
|
|
10
|
+
- **app, sdk:** bump Firebase Android 35, Apple 13, and JS 13
|
|
11
|
+
|
|
12
|
+
### Features
|
|
13
|
+
|
|
14
|
+
- **app, sdk:** bump Firebase Android 35, Apple 13, and JS 13 ([0ca03d1](https://github.com/invertase/react-native-firebase/commit/0ca03d11b0c271fb37cfbee8a0c3074a21969f0b))
|
|
15
|
+
- **database:** get() uses the native one-shot read on Android and web ([65cf63c](https://github.com/invertase/react-native-firebase/commit/65cf63c963b3914a4aa0e450caefae7a621d383b)), closes [firebase-ios-sdk#12168](https://github.com/invertase/firebase-ios-sdk/issues/12168)
|
|
16
|
+
|
|
17
|
+
### Bug Fixes
|
|
18
|
+
|
|
19
|
+
- **app,database:** resolve platform native module when bundlers apply package exports ([959d91f](https://github.com/invertase/react-native-firebase/commit/959d91fc79d7155d1d9a08e1f836789e6e6d6156))
|
|
20
|
+
- **database, android:** settle keepSynced promise after SDK apply ([61100f2](https://github.com/invertase/react-native-firebase/commit/61100f27700eda0b5f69ec21ebd991ca97ce316d))
|
|
21
|
+
- **database, android:** surface onDisconnect errors; cover deny reject arms ([1a4a4d6](https://github.com/invertase/react-native-firebase/commit/1a4a4d63e5f41042d9ddba55375b6a758f9829d5))
|
|
22
|
+
- **database:** decode iOS null sentinels before writes ([501bec8](https://github.com/invertase/react-native-firebase/commit/501bec812fab11614cae0622a93b6d16687819dd))
|
|
23
|
+
- **database:** keep get() from cancelling any keepSynced at the location on Android ([befb08c](https://github.com/invertase/react-native-firebase/commit/befb08c2f225ea65031ffa71ad173a68cf411dfd)), closes [firebase-android-sdk#8433](https://github.com/invertase/firebase-android-sdk/issues/8433)
|
|
24
|
+
- **database:** only fall back to once() for locations kept synced ([6a7eddb](https://github.com/invertase/react-native-firebase/commit/6a7eddb837f6c003eaa6f6da8697e4b402da4861))
|
|
25
|
+
- **database:** skip once retry after observer-shaped get errors ([6e18ddf](https://github.com/invertase/react-native-firebase/commit/6e18ddffa2f74445b843dc0d992b41232254ccb3))
|
|
26
|
+
|
|
6
27
|
## [26.4.0](https://github.com/invertase/react-native-firebase/compare/v26.3.3...v26.4.0) (2026-09-05)
|
|
7
28
|
|
|
8
29
|
### Bug Fixes
|
package/RNFBDatabase.podspec
CHANGED
|
@@ -29,7 +29,8 @@ Pod::Spec.new do |s|
|
|
|
29
29
|
s.ios.deployment_target = firebase_ios_target
|
|
30
30
|
s.macos.deployment_target = firebase_macos_target
|
|
31
31
|
s.tvos.deployment_target = firebase_tvos_target
|
|
32
|
-
s.
|
|
32
|
+
s.swift_version = '5.10'
|
|
33
|
+
s.source_files = 'ios/**/*.{h,m,mm,cpp,swift}'
|
|
33
34
|
s.private_header_files = [
|
|
34
35
|
'ios/RNFBDatabase/RNFBDatabaseModule.h',
|
|
35
36
|
'ios/RNFBDatabase/RNFBDatabaseReferenceModule.h',
|
|
@@ -38,11 +39,16 @@ Pod::Spec.new do |s|
|
|
|
38
39
|
'ios/RNFBDatabase/RNFBDatabaseTransactionModule.h',
|
|
39
40
|
'ios/RNFBDatabase/RNFBDatabaseQueryRegistry.h',
|
|
40
41
|
'ios/RNFBDatabase/RNFBDatabaseListenerRegistry.h',
|
|
42
|
+
'ios/RNFBDatabase/RNFBDatabaseNullSentinelDecoder.h',
|
|
41
43
|
'ios/generated/**/*.h',
|
|
42
44
|
]
|
|
43
|
-
|
|
45
|
+
# CocoaPods FileList uses FNM_PATHNAME: `ios/*UnitTests/**` does not match nested
|
|
46
|
+
# paths like `ios/RNFBDatabaseUnitTests/HostStubs/**` (those would compile into the pod).
|
|
47
|
+
s.exclude_files = 'ios/generated/RCTThirdPartyComponentsProvider.*', 'ios/generated/RCTAppDependencyProvider.*', 'ios/generated/RCTModuleProviders.*', 'ios/generated/RCTModulesConformingToProtocolsProvider.*', 'ios/generated/RCTUnstableModulesRequiringMainQueueSetupProvider.*', 'ios/*UnitTests/**/*'
|
|
44
48
|
|
|
49
|
+
# DEFINES_MODULE is required for the generated RNFBDatabase-Swift.h interface.
|
|
45
50
|
s.pod_target_xcconfig = {
|
|
51
|
+
"DEFINES_MODULE" => "YES",
|
|
46
52
|
"HEADER_SEARCH_PATHS" => "\"$(PODS_TARGET_SRCROOT)/ios/generated/RNFBDatabaseTurboModules\" \"$(PODS_TARGET_SRCROOT)/ios/generated\"",
|
|
47
53
|
"CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES" => "YES",
|
|
48
54
|
}
|
package/android/build.gradle
CHANGED
|
@@ -13,6 +13,7 @@ buildscript {
|
|
|
13
13
|
|
|
14
14
|
dependencies {
|
|
15
15
|
classpath("com.android.tools.build:gradle:8.4.0")
|
|
16
|
+
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${project.findProperty('kotlinVersion') ?: '2.4.0'}")
|
|
16
17
|
}
|
|
17
18
|
}
|
|
18
19
|
}
|
|
@@ -21,6 +22,8 @@ plugins {
|
|
|
21
22
|
id "io.invertase.gradle.build" version "1.5"
|
|
22
23
|
}
|
|
23
24
|
|
|
25
|
+
apply plugin: "org.jetbrains.kotlin.android"
|
|
26
|
+
|
|
24
27
|
def appProject
|
|
25
28
|
if (findProject(':@react-native-firebase_app')) {
|
|
26
29
|
appProject = project(':@react-native-firebase_app')
|
|
@@ -95,6 +98,8 @@ dependencies {
|
|
|
95
98
|
implementation "com.google.firebase:firebase-database"
|
|
96
99
|
|
|
97
100
|
testImplementation "junit:junit:4.13.2"
|
|
101
|
+
testImplementation "org.robolectric:robolectric:4.14.1"
|
|
102
|
+
testImplementation "org.mockito:mockito-core:5.14.2"
|
|
98
103
|
}
|
|
99
104
|
|
|
100
105
|
ReactNative.shared.applyPackageVersion()
|
package/android/src/main/java/io/invertase/firebase/database/RNFBDatabaseKeepSyncedRegistry.java
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
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 java.util.ArrayList;
|
|
21
|
+
import java.util.HashMap;
|
|
22
|
+
import java.util.HashSet;
|
|
23
|
+
import java.util.List;
|
|
24
|
+
import java.util.Map;
|
|
25
|
+
import java.util.Set;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Keeps the native one-shot get away from the app's keepSynced, by location.
|
|
29
|
+
*
|
|
30
|
+
* <p>When Query.get() misses the cache, it turns keepSynced on and then off for the location's
|
|
31
|
+
* default spec. Turning it off removes every keepSynced registration at the location, whatever its
|
|
32
|
+
* query, without unlistening, which cancels the app's keepSynced there (firebase-android-sdk#8433).
|
|
33
|
+
* So a location with anything kept synced is read with once(), and a keepSynced call on a location
|
|
34
|
+
* waits for the native gets in flight there.
|
|
35
|
+
*/
|
|
36
|
+
final class RNFBDatabaseKeepSyncedRegistry {
|
|
37
|
+
// Keys of the queries kept synced, by location.
|
|
38
|
+
private final Map<String, Set<String>> keptSynced = new HashMap<>();
|
|
39
|
+
// Native gets in flight, by location.
|
|
40
|
+
private final Map<String, Integer> nativeGets = new HashMap<>();
|
|
41
|
+
// keepSynced calls waiting for those gets, in call order, by location.
|
|
42
|
+
private final Map<String, List<Runnable>> deferred = new HashMap<>();
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Records keepSynced for the query with this key at the location, and runs {@code apply} now, or
|
|
46
|
+
* after the location's native gets complete (call order preserved). {@code apply} is the SDK
|
|
47
|
+
* {@code Query.keepSynced} call and any caller completion that must wait for it (for example
|
|
48
|
+
* settling a TurboModule Promise). A native get only starts while nothing at the location is kept
|
|
49
|
+
* synced, so the SDK has no keepSynced there in the meantime.
|
|
50
|
+
*/
|
|
51
|
+
synchronized void keepSynced(String location, String queryKey, boolean enabled, Runnable apply) {
|
|
52
|
+
Set<String> queryKeys = keptSynced.get(location);
|
|
53
|
+
if (enabled) {
|
|
54
|
+
if (queryKeys == null) {
|
|
55
|
+
queryKeys = new HashSet<>();
|
|
56
|
+
keptSynced.put(location, queryKeys);
|
|
57
|
+
}
|
|
58
|
+
queryKeys.add(queryKey);
|
|
59
|
+
} else if (queryKeys != null) {
|
|
60
|
+
queryKeys.remove(queryKey);
|
|
61
|
+
if (queryKeys.isEmpty()) {
|
|
62
|
+
keptSynced.remove(location);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (!nativeGets.containsKey(location)) {
|
|
67
|
+
apply.run();
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
List<Runnable> waiting = deferred.get(location);
|
|
71
|
+
if (waiting == null) {
|
|
72
|
+
waiting = new ArrayList<>();
|
|
73
|
+
deferred.put(location, waiting);
|
|
74
|
+
}
|
|
75
|
+
waiting.add(apply);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Counts a native get on the location, or returns false if anything there is kept synced. */
|
|
79
|
+
synchronized boolean beginNativeGet(String location) {
|
|
80
|
+
if (keptSynced.containsKey(location)) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
Integer inFlight = nativeGets.get(location);
|
|
84
|
+
nativeGets.put(location, inFlight == null ? 1 : inFlight + 1);
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Ends a native get. After the location's last one, runs the keepSynced calls that waited for it,
|
|
90
|
+
* in call order.
|
|
91
|
+
*/
|
|
92
|
+
synchronized void endNativeGet(String location) {
|
|
93
|
+
int inFlight = nativeGets.get(location) - 1;
|
|
94
|
+
if (inFlight > 0) {
|
|
95
|
+
nativeGets.put(location, inFlight);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
nativeGets.remove(location);
|
|
99
|
+
List<Runnable> waiting = deferred.remove(location);
|
|
100
|
+
if (waiting != null) {
|
|
101
|
+
for (Runnable apply : waiting) {
|
|
102
|
+
apply.run();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
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
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Per-query value/child listener maps. Unique [putValue]/[putChild]; callers [takeValue]/
|
|
25
|
+
* [takeChild] or [takeAll] then remove the SDK listener outside the HandleMap lock. Null values are
|
|
26
|
+
* not stored so [hasListeners] can use occupancy.
|
|
27
|
+
*
|
|
28
|
+
* Null-key note: [getValue]/[getChild]/[takeValue]/[takeChild]/[hasEventListener] accept
|
|
29
|
+
* `String?` and return null/false for a null key (Java `HashMap` interop). TurboModule/`ReadableMap`
|
|
30
|
+
* paths can pass a null `eventRegistrationKey` into remove/`hasEventListener`. Put keys stay
|
|
31
|
+
* non-null `String` (null-key put from Java NPEs via Kotlin null-check; swallowed at add call sites).
|
|
32
|
+
*/
|
|
33
|
+
internal class RNFBDatabaseListenerRegistry {
|
|
34
|
+
private val occupancyLock = Any()
|
|
35
|
+
private val valueListeners = RNFBHandleMap<String, Any>()
|
|
36
|
+
private val childListeners = RNFBHandleMap<String, Any>()
|
|
37
|
+
private var occupancy = 0
|
|
38
|
+
|
|
39
|
+
@Throws(RNFBHandleCollisionException::class)
|
|
40
|
+
fun putValue(
|
|
41
|
+
eventRegistrationKey: String,
|
|
42
|
+
listener: Any?,
|
|
43
|
+
) {
|
|
44
|
+
if (listener == null) {
|
|
45
|
+
throw NullPointerException("listener")
|
|
46
|
+
}
|
|
47
|
+
synchronized(occupancyLock) {
|
|
48
|
+
valueListeners.put(eventRegistrationKey, listener)
|
|
49
|
+
occupancy++
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
@Throws(RNFBHandleCollisionException::class)
|
|
54
|
+
fun putChild(
|
|
55
|
+
eventRegistrationKey: String,
|
|
56
|
+
listener: Any?,
|
|
57
|
+
) {
|
|
58
|
+
if (listener == null) {
|
|
59
|
+
throw NullPointerException("listener")
|
|
60
|
+
}
|
|
61
|
+
synchronized(occupancyLock) {
|
|
62
|
+
childListeners.put(eventRegistrationKey, listener)
|
|
63
|
+
occupancy++
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
fun getValue(eventRegistrationKey: String?): Any? = if (eventRegistrationKey == null) null else valueListeners.get(eventRegistrationKey)
|
|
68
|
+
|
|
69
|
+
fun getChild(eventRegistrationKey: String?): Any? = if (eventRegistrationKey == null) null else childListeners.get(eventRegistrationKey)
|
|
70
|
+
|
|
71
|
+
fun takeValue(eventRegistrationKey: String?): Any? {
|
|
72
|
+
synchronized(occupancyLock) {
|
|
73
|
+
if (eventRegistrationKey == null) {
|
|
74
|
+
return null
|
|
75
|
+
}
|
|
76
|
+
val listener = valueListeners.take(eventRegistrationKey)
|
|
77
|
+
if (listener != null) {
|
|
78
|
+
occupancy--
|
|
79
|
+
}
|
|
80
|
+
return listener
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
fun takeChild(eventRegistrationKey: String?): Any? {
|
|
85
|
+
synchronized(occupancyLock) {
|
|
86
|
+
if (eventRegistrationKey == null) {
|
|
87
|
+
return null
|
|
88
|
+
}
|
|
89
|
+
val listener = childListeners.take(eventRegistrationKey)
|
|
90
|
+
if (listener != null) {
|
|
91
|
+
occupancy--
|
|
92
|
+
}
|
|
93
|
+
return listener
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
fun takeAllValues(): List<Any> {
|
|
98
|
+
synchronized(occupancyLock) {
|
|
99
|
+
val remaining = valueListeners.takeAll()
|
|
100
|
+
occupancy -= remaining.size
|
|
101
|
+
return remaining
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
fun takeAllChildren(): List<Any> {
|
|
106
|
+
synchronized(occupancyLock) {
|
|
107
|
+
val remaining = childListeners.takeAll()
|
|
108
|
+
occupancy -= remaining.size
|
|
109
|
+
return remaining
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
fun takeAll(): List<Any> {
|
|
114
|
+
val remaining = ArrayList<Any>()
|
|
115
|
+
remaining.addAll(takeAllValues())
|
|
116
|
+
remaining.addAll(takeAllChildren())
|
|
117
|
+
return remaining
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
fun hasEventListener(eventRegistrationKey: String?): Boolean {
|
|
121
|
+
if (eventRegistrationKey == null) {
|
|
122
|
+
return false
|
|
123
|
+
}
|
|
124
|
+
return valueListeners.get(eventRegistrationKey) != null ||
|
|
125
|
+
childListeners.get(eventRegistrationKey) != null
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
fun hasListeners(): Boolean {
|
|
129
|
+
synchronized(occupancyLock) {
|
|
130
|
+
return occupancy > 0
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
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
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Cached query map. Unique [put]; callers [take], [takeIfIdle], or [takeAllAndRemove] then
|
|
25
|
+
* `removeAllEventListeners()` outside the HandleMap lock.
|
|
26
|
+
*
|
|
27
|
+
* Null-key note: [get]/[take]/[takeIfIdle] accept `String?` and return null / no-op for a null key
|
|
28
|
+
* (Java `HashMap` interop). TurboModule `off(queryKey, …)` can pass a null `queryKey`. Put keys stay
|
|
29
|
+
* non-null `String`.
|
|
30
|
+
*/
|
|
31
|
+
internal class RNFBDatabaseQueryRegistry {
|
|
32
|
+
private val map = RNFBHandleMap<String, DatabaseQueryHandle?>()
|
|
33
|
+
|
|
34
|
+
@Throws(RNFBHandleCollisionException::class)
|
|
35
|
+
fun put(
|
|
36
|
+
queryKey: String,
|
|
37
|
+
query: DatabaseQueryHandle?,
|
|
38
|
+
) {
|
|
39
|
+
map.put(queryKey, query)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
fun get(queryKey: String?): DatabaseQueryHandle? = if (queryKey == null) null else map.get(queryKey)
|
|
43
|
+
|
|
44
|
+
fun take(queryKey: String?): DatabaseQueryHandle? = if (queryKey == null) null else map.take(queryKey)
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Removes the mapping when the query has no listeners. Reads [DatabaseQueryHandle.hasListeners]
|
|
48
|
+
* outside the HandleMap lock (avoids nesting `occupancyLock`), then identity-takes and put-backs if
|
|
49
|
+
* listeners appeared.
|
|
50
|
+
*/
|
|
51
|
+
fun takeIfIdle(queryKey: String?) {
|
|
52
|
+
if (queryKey == null) {
|
|
53
|
+
return
|
|
54
|
+
}
|
|
55
|
+
val query = map.get(queryKey) ?: return
|
|
56
|
+
if (query.hasListeners() == true) {
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
val taken = map.takeIf(queryKey) { q -> q === query } ?: return
|
|
60
|
+
if (taken.hasListeners() == true) {
|
|
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
|
+
fun takeAllAndRemove() {
|
|
71
|
+
val remaining = map.takeAll()
|
|
72
|
+
for (query in remaining) {
|
|
73
|
+
query?.removeAllEventListeners()
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
package/android/src/main/java/io/invertase/firebase/database/RNFBDatabaseTransactionRegistry.kt
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
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
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Transaction handler map. Unique [put] (occupied id aborts the incoming handler, then throws);
|
|
25
|
+
* callers [take] or [takeAllAndAbort] then `abort()` outside the HandleMap lock. Replace is
|
|
26
|
+
* [registerReplacing] (atomic last wins).
|
|
27
|
+
*/
|
|
28
|
+
internal class RNFBDatabaseTransactionRegistry {
|
|
29
|
+
private val map = RNFBHandleMap<Int, DatabaseAbortable?>()
|
|
30
|
+
|
|
31
|
+
@Throws(RNFBHandleCollisionException::class)
|
|
32
|
+
fun put(
|
|
33
|
+
transactionId: Int,
|
|
34
|
+
handler: DatabaseAbortable?,
|
|
35
|
+
) {
|
|
36
|
+
try {
|
|
37
|
+
map.put(transactionId, handler)
|
|
38
|
+
} catch (collision: RNFBHandleCollisionException) {
|
|
39
|
+
handler?.abort()
|
|
40
|
+
throw collision
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Atomic replace (last wins). The replaced handler is not aborted (Firebase retry). */
|
|
45
|
+
fun registerReplacing(
|
|
46
|
+
transactionId: Int,
|
|
47
|
+
handler: DatabaseAbortable?,
|
|
48
|
+
) {
|
|
49
|
+
map.putReplacing(transactionId, handler)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
fun get(transactionId: Int): DatabaseAbortable? = map.get(transactionId)
|
|
53
|
+
|
|
54
|
+
fun take(transactionId: Int): DatabaseAbortable? = map.take(transactionId)
|
|
55
|
+
|
|
56
|
+
fun takeAndAbort(transactionId: Int) {
|
|
57
|
+
val handler = map.take(transactionId)
|
|
58
|
+
handler?.abort()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
fun takeAllAndAbort() {
|
|
62
|
+
val remaining = map.takeAll()
|
|
63
|
+
for (handler in remaining) {
|
|
64
|
+
handler?.abort()
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -21,6 +21,7 @@ import static io.invertase.firebase.database.UniversalFirebaseDatabaseCommon.get
|
|
|
21
21
|
|
|
22
22
|
import android.content.Context;
|
|
23
23
|
import com.google.android.gms.tasks.Task;
|
|
24
|
+
import com.google.android.gms.tasks.TaskCompletionSource;
|
|
24
25
|
import com.google.firebase.database.OnDisconnect;
|
|
25
26
|
import io.invertase.firebase.common.UniversalFirebaseModule;
|
|
26
27
|
import java.util.Map;
|
|
@@ -32,33 +33,125 @@ public class UniversalFirebaseDatabaseOnDisconnectModule extends UniversalFireba
|
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
Task<Void> onDisconnectCancel(String appName, String dbURL, String path) {
|
|
35
|
-
|
|
36
|
+
TaskCompletionSource<Void> taskCompletionSource = new TaskCompletionSource<>();
|
|
37
|
+
OnDisconnect onDisconnect = getDatabaseForApp(appName, dbURL).getReference(path).onDisconnect();
|
|
38
|
+
|
|
39
|
+
onDisconnect.cancel(
|
|
40
|
+
(databaseError, databaseReference) -> {
|
|
41
|
+
if (databaseError != null) {
|
|
42
|
+
taskCompletionSource.setException(
|
|
43
|
+
new UniversalDatabaseException(
|
|
44
|
+
databaseError.getCode(),
|
|
45
|
+
databaseError.getMessage(),
|
|
46
|
+
databaseError.toException()));
|
|
47
|
+
} else {
|
|
48
|
+
taskCompletionSource.setResult(null);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
return taskCompletionSource.getTask();
|
|
36
53
|
}
|
|
37
54
|
|
|
38
55
|
Task<Void> onDisconnectRemove(String appName, String dbURL, String path) {
|
|
39
|
-
|
|
56
|
+
TaskCompletionSource<Void> taskCompletionSource = new TaskCompletionSource<>();
|
|
57
|
+
OnDisconnect onDisconnect = getDatabaseForApp(appName, dbURL).getReference(path).onDisconnect();
|
|
58
|
+
|
|
59
|
+
onDisconnect.removeValue(
|
|
60
|
+
(databaseError, databaseReference) -> {
|
|
61
|
+
if (databaseError != null) {
|
|
62
|
+
taskCompletionSource.setException(
|
|
63
|
+
new UniversalDatabaseException(
|
|
64
|
+
databaseError.getCode(),
|
|
65
|
+
databaseError.getMessage(),
|
|
66
|
+
databaseError.toException()));
|
|
67
|
+
} else {
|
|
68
|
+
taskCompletionSource.setResult(null);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
return taskCompletionSource.getTask();
|
|
40
73
|
}
|
|
41
74
|
|
|
42
75
|
Task<Void> onDisconnectSet(String appName, String dbURL, String path, Object value) {
|
|
43
|
-
|
|
76
|
+
TaskCompletionSource<Void> taskCompletionSource = new TaskCompletionSource<>();
|
|
77
|
+
OnDisconnect onDisconnect = getDatabaseForApp(appName, dbURL).getReference(path).onDisconnect();
|
|
78
|
+
|
|
79
|
+
onDisconnect.setValue(
|
|
80
|
+
value,
|
|
81
|
+
(databaseError, databaseReference) -> {
|
|
82
|
+
if (databaseError != null) {
|
|
83
|
+
taskCompletionSource.setException(
|
|
84
|
+
new UniversalDatabaseException(
|
|
85
|
+
databaseError.getCode(),
|
|
86
|
+
databaseError.getMessage(),
|
|
87
|
+
databaseError.toException()));
|
|
88
|
+
} else {
|
|
89
|
+
taskCompletionSource.setResult(null);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
return taskCompletionSource.getTask();
|
|
44
94
|
}
|
|
45
95
|
|
|
46
96
|
Task<Void> onDisconnectSetWithPriority(
|
|
47
97
|
String appName, String dbURL, String path, Object value, Object priority) {
|
|
98
|
+
TaskCompletionSource<Void> taskCompletionSource = new TaskCompletionSource<>();
|
|
48
99
|
OnDisconnect onDisconnect = getDatabaseForApp(appName, dbURL).getReference(path).onDisconnect();
|
|
49
100
|
|
|
50
101
|
if (priority instanceof String) {
|
|
51
|
-
|
|
102
|
+
onDisconnect.setValue(
|
|
103
|
+
value,
|
|
104
|
+
(String) priority,
|
|
105
|
+
(databaseError, databaseReference) -> {
|
|
106
|
+
if (databaseError != null) {
|
|
107
|
+
taskCompletionSource.setException(
|
|
108
|
+
new UniversalDatabaseException(
|
|
109
|
+
databaseError.getCode(),
|
|
110
|
+
databaseError.getMessage(),
|
|
111
|
+
databaseError.toException()));
|
|
112
|
+
} else {
|
|
113
|
+
taskCompletionSource.setResult(null);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
52
116
|
} else {
|
|
53
|
-
|
|
117
|
+
onDisconnect.setValue(
|
|
118
|
+
value,
|
|
119
|
+
(double) priority,
|
|
120
|
+
(databaseError, databaseReference) -> {
|
|
121
|
+
if (databaseError != null) {
|
|
122
|
+
taskCompletionSource.setException(
|
|
123
|
+
new UniversalDatabaseException(
|
|
124
|
+
databaseError.getCode(),
|
|
125
|
+
databaseError.getMessage(),
|
|
126
|
+
databaseError.toException()));
|
|
127
|
+
} else {
|
|
128
|
+
taskCompletionSource.setResult(null);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
54
131
|
}
|
|
132
|
+
|
|
133
|
+
return taskCompletionSource.getTask();
|
|
55
134
|
}
|
|
56
135
|
|
|
57
136
|
Task<Void> onDisconnectUpdate(
|
|
58
137
|
String appName, String dbURL, String path, Map<String, Object> values) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
138
|
+
TaskCompletionSource<Void> taskCompletionSource = new TaskCompletionSource<>();
|
|
139
|
+
OnDisconnect onDisconnect = getDatabaseForApp(appName, dbURL).getReference(path).onDisconnect();
|
|
140
|
+
|
|
141
|
+
onDisconnect.updateChildren(
|
|
142
|
+
values,
|
|
143
|
+
(databaseError, databaseReference) -> {
|
|
144
|
+
if (databaseError != null) {
|
|
145
|
+
taskCompletionSource.setException(
|
|
146
|
+
new UniversalDatabaseException(
|
|
147
|
+
databaseError.getCode(),
|
|
148
|
+
databaseError.getMessage(),
|
|
149
|
+
databaseError.toException()));
|
|
150
|
+
} else {
|
|
151
|
+
taskCompletionSource.setResult(null);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
return taskCompletionSource.getTask();
|
|
63
156
|
}
|
|
64
157
|
}
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
114
|
-
promise, task.getException());
|
|
109
|
+
rejectPromiseDatabaseException(promise, task.getException());
|
|
115
110
|
}
|
|
116
111
|
});
|
|
117
112
|
}
|