Skip to content

Commit 487eeae

Browse files
committed
feat: updated to switch over to using React Native event library for replication status change event
1 parent eb62bb2 commit 487eeae

File tree

5 files changed

+158
-14
lines changed

5 files changed

+158
-14
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>IDEDidComputeMac32BitWarning</key>
6+
<true/>
7+
</dict>
8+
</plist>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
#import <React/RCTBridgeModule.h>
2+
#import <React/RCTEventEmitter.h>
23
#import <React/RCTViewManager.h>

ios/CblReactnative.mm

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#import <React/RCTBridgeModule.h>
2+
#import <React/RCTEventEmitter.h>
23

3-
@interface RCT_EXTERN_MODULE(CblReactnative, NSObject)
4+
@interface RCT_EXTERN_MODULE(CblReactnative, RCTEventEmitter)
45

56
// MARK: - Collection Functions
67

@@ -227,7 +228,8 @@ @interface RCT_EXTERN_MODULE(CblReactnative, NSObject)
227228
RCT_EXTERN_METHOD(replicator_AddChangeListener:
228229
(NSString *)changeListenerToken
229230
withReplicatorId:(NSString *)replicatorId
230-
withCallback:(RCTResponseSenderBlock)callback)
231+
withResolver:(RCTPromiseResolveBlock)resolve
232+
withRejecter:(RCTPromiseRejectBlock)reject)
231233

232234
RCT_EXTERN_METHOD(replicator_Cleanup:
233235
(NSString *)replicatorId

ios/CblReactnative.swift

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ import CouchbaseLiteSwift
44
@objc(
55
CblReactnative
66
)
7-
class CblReactnative: NSObject {
7+
class CblReactnative: RCTEventEmitter {
88

99
// MARK: - Member Properties
10+
private var hasListeners = false
1011
var databaseChangeListeners = [String: Any]()
1112

1213
var collectionChangeListeners = [String: Any]()
@@ -22,6 +23,47 @@ class CblReactnative: NSObject {
2223
// Create a serial DispatchQueue for background tasks
2324
let backgroundQueue = DispatchQueue(label: "com.cblite.reactnative.backgroundQueue")
2425

26+
override init() {
27+
super.init()
28+
}
29+
// MARK: - Setup Notifications
30+
31+
override func startObserving() {
32+
hasListeners = true
33+
}
34+
35+
override func stopObserving() {
36+
hasListeners = false
37+
}
38+
39+
override func supportedEvents() -> [String]! {
40+
return ["collectionChange","collectionDocumentChange","queryChange","replicatorStatusChange","replicatorDocumentChange"]
41+
}
42+
43+
@objc override static func requiresMainQueueSetup() -> Bool {
44+
return true
45+
}
46+
47+
@objc func handleNotification(_ notification: Notification) {
48+
if self.hasListeners {
49+
let userInfo = notification.userInfo ?? [:]
50+
switch notification.name {
51+
case .collectionChange:
52+
sendEvent(withName: "collectionChange", body: userInfo)
53+
case .collectionDocumentChange:
54+
sendEvent(withName: "collectionDocumentChange", body: userInfo)
55+
case .queryChange:
56+
sendEvent(withName: "queryChange", body: userInfo)
57+
case .replicatorStatusChange:
58+
sendEvent(withName: "queryChange", body: userInfo)
59+
case .replicatorDocumentChange:
60+
sendEvent(withName: "replicatorDocumentChange", body: userInfo)
61+
default:
62+
break
63+
}
64+
}
65+
}
66+
2567
// MARK: - Collection Functions
2668

2769
@objc(collection_CreateCollection:fromDatabaseWithName:fromScopeWithName:withResolver:withRejecter:)
@@ -1121,28 +1163,32 @@ class CblReactnative: NSObject {
11211163

11221164
// MARK: - Replicator Functions
11231165

1124-
@objc(replicator_AddChangeListener:withReplicatorId:withCallback:)
1166+
@objc(replicator_AddChangeListener:withReplicatorId:withResolver:
1167+
withRejecter:)
11251168
func replicator_AddChangeListener(
11261169
changeListenerToken: NSString,
11271170
replicatorId: NSString,
1128-
callback: @escaping RCTResponseSenderBlock ) -> Void {
1171+
resolve: @escaping RCTPromiseResolveBlock,
1172+
reject: @escaping RCTPromiseRejectBlock)
1173+
{
11291174
backgroundQueue.async {
11301175
var errorMessage = ""
11311176
var resultData = NSMutableDictionary()
11321177
let replId = String(replicatorId)
11331178
let token = String(changeListenerToken)
11341179
guard let replicator = ReplicatorManager.shared.getReplicator(replicatorId: replId) else {
11351180
errorMessage = "No such replicator found for id \(replId)"
1136-
callback([resultData, errorMessage])
1181+
reject("REPLICATOR_ERROR", errorMessage, nil)
11371182
return
11381183
}
11391184

11401185
let listener = replicator.addChangeListener(withQueue: self.backgroundQueue, { change in
11411186
let statusJson = ReplicatorHelper.generateReplicatorStatusJson(change.status)
11421187
resultData.setValue(statusJson, forKey: "status")
1143-
callback([resultData, errorMessage])
1188+
NotificationCenter.default.post(name: .replicatorStatusChange, object: nil, userInfo: resultData as? [AnyHashable : Any])
11441189
})
11451190
self.replicatorChangeListeners[token] = listener
1191+
resolve(nil)
11461192
}
11471193
}
11481194

@@ -1545,3 +1591,11 @@ class CblReactnative: NSObject {
15451591
}
15461592
}
15471593
}
1594+
1595+
extension Notification.Name {
1596+
static let collectionChange = Notification.Name("collectionChange")
1597+
static let collectionDocumentChange = Notification.Name("collectionDocumentChange")
1598+
static let queryChange = Notification.Name("queryChange")
1599+
static let replicatorStatusChange = Notification.Name("replicatorStatusChange")
1600+
static let replicatorDocumentChange = Notification.Name("replicatorDocumentChange")
1601+
}

src/CblReactNativeEngine.tsx

Lines changed: 86 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
import { NativeModules, Platform } from 'react-native';
1+
import { NativeEventEmitter, NativeModules, Platform } from 'react-native';
22
import {
33
CollectionChangeListenerArgs,
44
ICoreEngine,
55
ListenerCallback,
6-
ListenerHandle,
76
CollectionArgs,
87
CollectionCreateIndexArgs,
98
CollectionDeleteDocumentArgs,
@@ -57,6 +56,25 @@ export class CblReactNativeEngine implements ICoreEngine {
5756
_defaultCollectionName = '_default';
5857
_defaultScopeName = '_default';
5958

59+
//event name mapping for the native side of the module
60+
_eventReplicatorStatusChange = 'replicatorStatusChange';
61+
_eventReplicatorDocumentChange = 'replicatorDocumentChange';
62+
_eventCollectionChange = 'collectionChange';
63+
_eventCollectionDocumentChange = 'collectionDocumentChange';
64+
_eventQueryChange = 'queryChange';
65+
66+
//used to listen to replicator change events for both status and document changes
67+
private _isReplicatorStatusChangeEventSetup: boolean = false;
68+
private _replicatorChangeListeners: Map<string, ListenerCallback> = new Map();
69+
private _replicatorStatusChangeStopListener: () => void | undefined =
70+
undefined;
71+
72+
private _replicatorDocumentChangeListeners: Map<string, ListenerCallback> =
73+
new Map();
74+
private _replicatorDocumentChangeStopListener: () => void | undefined =
75+
undefined;
76+
private _isReplicatorDocumentChangeEventSetup: boolean = false;
77+
6078
private static readonly LINKING_ERROR =
6179
`The package 'cbl-reactnative' doesn't seem to be linked. Make sure: \n\n` +
6280
Platform.select({ ios: "- You have run 'pod install'\n", default: '' }) +
@@ -74,10 +92,18 @@ export class CblReactNativeEngine implements ICoreEngine {
7492
}
7593
);
7694

95+
private _eventEmitter = new NativeEventEmitter(this.CblReactNative);
96+
7797
constructor() {
7898
EngineLocator.registerEngine(EngineLocator.key, this);
7999
}
80100

101+
//startListeningEvents - used to listen to events from the native side of the module. Implements Native change listeners for Couchbase Lite
102+
startListeningEvents = (event: string, callback: any) => {
103+
const subscription = this._eventEmitter.addListener(event, callback);
104+
return () => subscription.remove();
105+
};
106+
81107
collection_AddChangeListener(
82108
args: CollectionChangeListenerArgs,
83109
lcb: ListenerCallback
@@ -748,18 +774,55 @@ export class CblReactNativeEngine implements ICoreEngine {
748774
args: ReplicationChangeListenerArgs,
749775
lcb: ListenerCallback
750776
): Promise<void> {
777+
//need to track the listener callback for later use due to how React Native events work. Events are global so we need to first find which callback to call, we could have multiple replicators registered
778+
//https://reactnative.dev/docs/native-modules-ios#sending-events-to-javascript
779+
if (this._replicatorChangeListeners.has(args.changeListenerToken)) {
780+
throw new Error(
781+
'ERROR: changeListenerToken already exists and is registered to listen to callbacks, cannot add a new one'
782+
);
783+
}
784+
//if the event listener is not setup, then set up the listener.
785+
//Event listener only needs to be setup once for any replicators in memory
786+
if (!this._isReplicatorStatusChangeEventSetup) {
787+
this._replicatorDocumentChangeStopListener = this.startListeningEvents(
788+
this._eventReplicatorStatusChange,
789+
(results: any[]) => {
790+
const token = results[0] as string;
791+
const data = results[1];
792+
const error = results[2];
793+
if (token === undefined || token === null || token.length === 0) {
794+
throw new Error(
795+
'ERROR: No token to resolve back to proper callback'
796+
);
797+
}
798+
const callback = this._replicatorChangeListeners.get(token);
799+
if (callback !== undefined) {
800+
callback(data, error);
801+
} else {
802+
throw new Error(
803+
`Error: Could not found callback method for token: ${token}.`
804+
);
805+
}
806+
}
807+
);
808+
this._isReplicatorStatusChangeEventSetup = true;
809+
}
751810
return new Promise((resolve, reject) => {
752811
this.CblReactNative.replicator_AddChangeListener(
753812
args.changeListenerToken,
754-
args.replicatorId,
755-
(results: [any, any]) => {
756-
lcb(results[0], results[1]);
757-
}
813+
args.replicatorId
758814
).then(
759815
() => {
816+
//add token to change listener map
817+
this._replicatorChangeListeners.set(args.changeListenerToken, lcb);
760818
resolve();
761819
},
762820
(error: any) => {
821+
//stop the event listening if there is an error and no other tokens are present, thus no need to listen to events
822+
if (this._replicatorChangeListeners.size === 0) {
823+
this._replicatorStatusChangeStopListener();
824+
this._isReplicatorStatusChangeEventSetup = false;
825+
}
763826
reject(error);
764827
}
765828
);
@@ -856,7 +919,23 @@ export class CblReactNativeEngine implements ICoreEngine {
856919
replicator_RemoveChangeListener(
857920
args: ReplicationChangeListenerArgs
858921
): Promise<void> {
859-
return Promise.resolve(undefined);
922+
return new Promise((resolve, reject) => {
923+
this.CblReactNative.replicator_RemoveChangeListener(
924+
args.replicatorId,
925+
args.changeListenerToken
926+
).then(
927+
() => {
928+
//remove the listener callback from the map
929+
if (this._replicatorChangeListeners.has(args.changeListenerToken)) {
930+
this._replicatorChangeListeners.delete(args.changeListenerToken);
931+
}
932+
resolve();
933+
},
934+
(error: any) => {
935+
reject(error);
936+
}
937+
);
938+
});
860939
}
861940

862941
replicator_ResetCheckpoint(args: ReplicatorArgs): Promise<void> {

0 commit comments

Comments
 (0)