Skip to content

Commit 2a88bcd

Browse files
author
Wojtach
committed
feat: added collection document change listener
1 parent ac18546 commit 2a88bcd

File tree

3 files changed

+200
-68
lines changed

3 files changed

+200
-68
lines changed

ios/CblReactnative.mm

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,22 @@ @interface RCT_EXTERN_MODULE(CblReactnative, RCTEventEmitter)
1515
)
1616

1717
RCT_EXTERN_METHOD(
18-
collection_RemoveChangeListener:(NSString *)changeListenerToken
18+
collection_AddDocumentChangeListener:(NSString *)changeListenerToken
19+
forDocumentWithId:(NSString *)documentId
1920
fromCollectionWithName:(NSString *)collectionName
2021
fromDatabaseWithName:(NSString *)name
2122
fromScopeWithName:(NSString *)scopeName
2223
withResolver:(RCTPromiseResolveBlock)resolve
2324
withRejecter:(RCTPromiseRejectBlock)reject
2425
)
2526

27+
28+
RCT_EXTERN_METHOD(
29+
collection_RemoveChangeListener:(NSString *)changeListenerToken
30+
withResolver:(RCTPromiseResolveBlock)resolve
31+
withRejecter:(RCTPromiseRejectBlock)reject
32+
)
33+
2634
RCT_EXTERN_METHOD(collection_CreateCollection:
2735
(NSString *) collectionName
2836
fromDatabaseWithName:(NSString *) name

ios/CblReactnative.swift

Lines changed: 111 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -57,81 +57,143 @@ class CblReactnative: RCTEventEmitter {
5757
}
5858

5959
// MARK: - Collection Functions
60-
@objc(collection_AddChangeListener:fromCollectionWithName:fromDatabaseWithName:fromScopeWithName:withResolver:withRejecter:)
61-
func collection_AddChangeListener(
62-
changeListenerToken: NSString,
63-
collectionName: NSString,
64-
name: NSString,
65-
scopeName: NSString,
66-
resolve: @escaping RCTPromiseResolveBlock,
67-
reject: @escaping RCTPromiseRejectBlock
68-
) -> Void {
69-
let (isError, args) = DataAdapter.shared.adaptCollectionArgs(name: name, collectionName: collectionName, scopeName: scopeName, reject: reject)
70-
let (isTokenError, token) = DataAdapter.shared.adaptNonEmptyString(value: changeListenerToken, propertyName: "changeListenerToken", reject: reject)
60+
@objc(collection_AddChangeListener:fromCollectionWithName:fromDatabaseWithName:fromScopeWithName:withResolver:withRejecter:)
61+
func collection_AddChangeListener(
62+
changeListenerToken: NSString,
63+
collectionName: NSString,
64+
name: NSString,
65+
scopeName: NSString,
66+
resolve: @escaping RCTPromiseResolveBlock,
67+
reject: @escaping RCTPromiseRejectBlock
68+
) -> Void {
69+
let (isError, args) = DataAdapter.shared.adaptCollectionArgs(name: name, collectionName: collectionName, scopeName: scopeName, reject: reject)
70+
let (isTokenError, token) = DataAdapter.shared.adaptNonEmptyString(value: changeListenerToken, propertyName: "changeListenerToken", reject: reject)
7171

72-
if isError || isTokenError {
73-
return
74-
}
72+
if isError || isTokenError {
73+
return
74+
}
7575

76-
backgroundQueue.async {
77-
do {
78-
guard let collection = try CollectionManager.shared.getCollection(
79-
args.collectionName,
80-
scopeName: args.scopeName,
81-
databaseName: args.databaseName
82-
) else {
83-
reject("DATABASE_ERROR", "Could not find collection", nil)
84-
return
85-
}
86-
87-
let listener = collection.addChangeListener(queue: self.backgroundQueue) { [weak self] (change) in
88-
guard let self = self else {
89-
// Log.log(domain: .database, level: .warning, message: "Unable to notify changes as the handler object was released")
76+
backgroundQueue.async {
77+
do {
78+
guard let collection = try CollectionManager.shared.getCollection(
79+
args.collectionName,
80+
scopeName: args.scopeName,
81+
databaseName: args.databaseName
82+
) else {
83+
reject("DATABASE_ERROR", "Could not find collection", nil)
9084
return
9185
}
86+
87+
let listener = collection.addChangeListener(queue: self.backgroundQueue) { [weak self] (change) in
88+
guard let self = self else { return }
9289

93-
// Format the data to match the CollectionChange interface
94-
let resultData = NSMutableDictionary()
95-
resultData.setValue(token, forKey: "token")
96-
resultData.setValue(change.documentIDs, forKey: "documentIDs")
90+
// Format the data to match the CollectionChange interface
91+
let resultData = NSMutableDictionary()
92+
resultData.setValue(token, forKey: "token")
93+
resultData.setValue(change.documentIDs, forKey: "documentIDs")
9794

98-
// Use DataAdapter to convert the Collection to a consistent dictionary format
99-
let collectionDict = DataAdapter.shared.adaptCollectionToNSDictionary(collection, databaseName: args.databaseName)
100-
resultData.setValue(collectionDict, forKey: "collection")
95+
// Use DataAdapter to convert the Collection to a consistent dictionary format
96+
let collectionDict = DataAdapter.shared.adaptCollectionToNSDictionary(collection, databaseName: args.databaseName)
97+
resultData.setValue(collectionDict, forKey: "collection")
10198

102-
self.sendEvent(withName: self.kCollectionChange, body: resultData)
103-
}
99+
self.sendEvent(withName: self.kCollectionChange, body: resultData)
100+
}
104101

105-
self.collectionChangeListeners[token] = listener
106-
resolve(nil)
107-
} catch let error as NSError {
108-
reject("DATABASE_ERROR", error.localizedDescription, nil)
109-
} catch {
110-
reject("DATABASE_ERROR", error.localizedDescription, nil)
102+
self.collectionChangeListeners[token] = listener
103+
resolve(nil)
104+
} catch let error as NSError {
105+
reject("DATABASE_ERROR", error.localizedDescription, nil)
106+
} catch {
107+
reject("DATABASE_ERROR", error.localizedDescription, nil)
108+
}
111109
}
112110
}
113-
}
114111

115-
@objc(collection_RemoveChangeListener:fromCollectionWithName:fromDatabaseWithName:fromScopeWithName:withResolver:withRejecter:)
116-
func collection_RemoveChangeListener(
112+
@objc(collection_AddDocumentChangeListener:forDocumentWithId:fromCollectionWithName:fromDatabaseWithName:fromScopeWithName:withResolver:withRejecter:)
113+
func collection_AddDocumentChangeListener(
117114
changeListenerToken: NSString,
115+
documentId: NSString,
118116
collectionName: NSString,
119117
name: NSString,
120118
scopeName: NSString,
121119
resolve: @escaping RCTPromiseResolveBlock,
122120
reject: @escaping RCTPromiseRejectBlock
121+
) -> Void {
122+
let (isError, args) = DataAdapter.shared.adaptCollectionArgs(name: name, collectionName: collectionName, scopeName: scopeName, reject: reject)
123+
let (isTokenError, token) = DataAdapter.shared.adaptNonEmptyString(value: changeListenerToken, propertyName: "changeListenerToken", reject: reject)
124+
let (isDocIdError, docId) = DataAdapter.shared.adaptNonEmptyString(value: documentId, propertyName: "documentId", reject: reject)
125+
126+
if isError || isTokenError || isDocIdError {
127+
return
128+
}
129+
130+
backgroundQueue.async {
131+
do {
132+
guard let collection = try CollectionManager.shared.getCollection(
133+
args.collectionName,
134+
scopeName: args.scopeName,
135+
databaseName: args.databaseName
136+
) else {
137+
reject("DATABASE_ERROR", "Could not find collection", nil)
138+
return
139+
}
140+
141+
let listener = collection.addDocumentChangeListener(id: docId, queue: self.backgroundQueue) { [weak self] (change) in
142+
guard let self = self else { return }
143+
144+
let resultData = NSMutableDictionary()
145+
resultData.setValue(token, forKey: "token")
146+
resultData.setValue(change.documentID, forKey: "documentId")
147+
148+
let collectionData = NSMutableDictionary()
149+
collectionData.setValue(collection.name, forKey: "name")
150+
collectionData.setValue(collection.scope.name, forKey: "scopeName")
151+
collectionData.setValue(args.databaseName, forKey: "databaseName")
152+
resultData.setValue(collectionData, forKey: "collection")
153+
154+
resultData.setValue(change.database.name, forKey: "database")
155+
156+
self.sendEvent(withName: self.kCollectionDocumentChange, body: resultData)
157+
}
158+
159+
self.collectionDocumentChangeListeners[token] = listener
160+
resolve(nil)
161+
} catch let error as NSError {
162+
reject("DATABASE_ERROR", error.localizedDescription, nil)
163+
} catch {
164+
reject("DATABASE_ERROR", error.localizedDescription, nil)
165+
}
166+
}
167+
}
168+
169+
170+
@objc(collection_RemoveChangeListener:withResolver:withRejecter:)
171+
func collection_RemoveChangeListener(
172+
changeListenerToken: NSString,
173+
resolve: @escaping RCTPromiseResolveBlock,
174+
reject: @escaping RCTPromiseRejectBlock
123175
) -> Void {
124176
let token = String(changeListenerToken)
125177

126178
backgroundQueue.async {
179+
// Check for collection change listeners
127180
if let listener = self.collectionChangeListeners[token] as? ListenerToken {
128-
// Remove the listener
129181
listener.remove()
130182
self.collectionChangeListeners.removeValue(forKey: token)
131183
resolve(nil)
132-
} else {
133-
reject("DATABASE_ERROR", "No listener found for token \(token)", nil)
184+
return
185+
}
186+
187+
// Check for document change listeners
188+
if let listener = self.collectionDocumentChangeListeners[token] as? ListenerToken {
189+
listener.remove()
190+
self.collectionDocumentChangeListeners.removeValue(forKey: token)
191+
resolve(nil)
192+
return
134193
}
194+
195+
// No listener found with this token
196+
reject("DATABASE_ERROR", "No listener found for token \(token)", nil)
135197
}
136198
}
137199

src/CblReactNativeEngine.tsx

Lines changed: 80 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,6 @@ export class CblReactNativeEngine implements ICoreEngine {
140140
): Promise<void> {
141141
return new Promise((resolve, reject) => {
142142
const token = args.changeListenerToken;
143-
const subscriptionKey = `${args.name}.${args.scopeName}.${args.collectionName}_${token}`;
144143

145144
if (this._collectionChangeListeners.has(token)) {
146145
reject(new Error('Change listener token already exists'));
@@ -160,7 +159,7 @@ export class CblReactNativeEngine implements ICoreEngine {
160159
}
161160
);
162161

163-
this._emitterSubscriptions.set(subscriptionKey, subscription);
162+
this._emitterSubscriptions.set(token, subscription);
164163
this._collectionChangeListeners.set(token, lcb);
165164

166165
this.CblReactNative.collection_AddChangeListener(
@@ -176,14 +175,51 @@ export class CblReactNativeEngine implements ICoreEngine {
176175
});
177176
}
178177

179-
// eslint-disable-next-line
180178
collection_AddDocumentChangeListener(
181-
// eslint-disable-next-line
182179
args: DocumentChangeListenerArgs,
183-
// eslint-disable-next-line
184180
lcb: ListenerCallback
185181
): Promise<void> {
186-
return Promise.resolve(undefined);
182+
return new Promise((resolve, reject) => {
183+
const token = args.changeListenerToken;
184+
185+
if (this._collectionChangeListeners.has(token)) {
186+
reject(new Error('Document change listener token already exists'));
187+
return;
188+
}
189+
190+
const subscription = this.startListeningEvents(
191+
this._eventCollectionDocumentChange,
192+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
193+
(results: any) => {
194+
if (results.token === token) {
195+
this.debugLog(
196+
`::DEBUG:: Received document change event for token: ${token}`
197+
);
198+
lcb(results);
199+
}
200+
}
201+
);
202+
203+
this._emitterSubscriptions.set(token, subscription);
204+
this._collectionChangeListeners.set(token, lcb);
205+
206+
this.CblReactNative.collection_AddDocumentChangeListener(
207+
token,
208+
args.documentId,
209+
args.collectionName,
210+
args.name,
211+
args.scopeName
212+
).then(
213+
() => resolve(),
214+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
215+
(error: any) => {
216+
this._emitterSubscriptions.delete(token);
217+
this._collectionChangeListeners.delete(token);
218+
subscription.remove();
219+
reject(error);
220+
}
221+
);
222+
});
187223
}
188224

189225
collection_CreateCollection(args: CollectionArgs): Promise<Collection> {
@@ -479,13 +515,11 @@ export class CblReactNativeEngine implements ICoreEngine {
479515
): Promise<void> {
480516
return new Promise((resolve, reject) => {
481517
const token = args.changeListenerToken;
482-
const collectionId = `${args.name}.${args.scopeName}.${args.collectionName}`;
483-
const subscriptionKey = `${collectionId}_${token}`;
484518

485519
// Remove the subscription
486-
if (this._emitterSubscriptions.has(subscriptionKey)) {
487-
this._emitterSubscriptions.get(subscriptionKey)?.remove();
488-
this._emitterSubscriptions.delete(subscriptionKey);
520+
if (this._emitterSubscriptions.has(token)) {
521+
this._emitterSubscriptions.get(token)?.remove();
522+
this._emitterSubscriptions.delete(token);
489523
}
490524

491525
// Remove the listener from the collection listeners map
@@ -497,12 +531,7 @@ export class CblReactNativeEngine implements ICoreEngine {
497531
}
498532

499533
// Remove the listener from the native side
500-
this.CblReactNative.collection_RemoveChangeListener(
501-
token,
502-
args.collectionName,
503-
args.name,
504-
args.scopeName
505-
).then(
534+
this.CblReactNative.collection_RemoveChangeListener(token).then(
506535
() => {
507536
this.debugLog(
508537
`::DEBUG:: collection_RemoveChangeListener completed for token: ${token}`
@@ -524,7 +553,40 @@ export class CblReactNativeEngine implements ICoreEngine {
524553
// eslint-disable-next-line
525554
args: CollectionChangeListenerArgs
526555
): Promise<void> {
527-
return Promise.resolve(undefined);
556+
return new Promise((resolve, reject) => {
557+
const token = args.changeListenerToken;
558+
559+
// Remove the subscription
560+
if (this._emitterSubscriptions.has(token)) {
561+
this._emitterSubscriptions.get(token)?.remove();
562+
this._emitterSubscriptions.delete(token);
563+
}
564+
565+
// Remove the listener from the document listeners map
566+
if (this._collectionChangeListeners.has(token)) {
567+
this._collectionChangeListeners.delete(token);
568+
} else {
569+
reject(new Error(`No document listener found with token: ${token}`));
570+
return;
571+
}
572+
573+
// Remove the listener from the native side
574+
this.CblReactNative.collection_RemoveChangeListener(token).then(
575+
() => {
576+
this.debugLog(
577+
`::DEBUG:: collection_RemoveDocumentChangeListener completed for token: ${token}`
578+
);
579+
resolve();
580+
},
581+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
582+
(error: any) => {
583+
this.debugLog(
584+
`::DEBUG:: collection_RemoveDocumentChangeListener Error: ${error}`
585+
);
586+
reject(error);
587+
}
588+
);
589+
});
528590
}
529591

530592
collection_Save(

0 commit comments

Comments
 (0)