Skip to content

Commit dd6d1c7

Browse files
author
Wojtach
committed
feat: added collection change listener
1 parent 04c10fb commit dd6d1c7

File tree

8 files changed

+180
-9
lines changed

8 files changed

+180
-9
lines changed

.prettierignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
android/
22
build/
3-
expo-example/
3+
# expo-example/
44
node_modules/
55
dist/
66
lib/

ios/CblReactnative.mm

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,24 @@ @interface RCT_EXTERN_MODULE(CblReactnative, RCTEventEmitter)
55

66
// MARK: - Collection Functions
77

8+
RCT_EXTERN_METHOD(
9+
collection_AddChangeListener:(NSString *)changeListenerToken
10+
fromCollectionWithName:(NSString *)collectionName
11+
fromDatabaseWithName:(NSString *)name
12+
fromScopeWithName:(NSString *)scopeName
13+
withResolver:(RCTPromiseResolveBlock)resolve
14+
withRejecter:(RCTPromiseRejectBlock)reject
15+
)
16+
17+
RCT_EXTERN_METHOD(
18+
collection_RemoveChangeListener:(NSString *)changeListenerToken
19+
fromCollectionWithName:(NSString *)collectionName
20+
fromDatabaseWithName:(NSString *)name
21+
fromScopeWithName:(NSString *)scopeName
22+
withResolver:(RCTPromiseResolveBlock)resolve
23+
withRejecter:(RCTPromiseRejectBlock)reject
24+
)
25+
826
RCT_EXTERN_METHOD(collection_CreateCollection:
927
(NSString *) collectionName
1028
fromDatabaseWithName:(NSString *) name

ios/CblReactnative.swift

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,84 @@ 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)
71+
72+
if isError || isTokenError {
73+
return
74+
}
75+
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")
90+
return
91+
}
92+
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")
97+
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")
101+
102+
self.sendEvent(withName: self.kCollectionChange, body: resultData)
103+
}
104+
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)
111+
}
112+
}
113+
}
114+
115+
@objc(collection_RemoveChangeListener:fromCollectionWithName:fromDatabaseWithName:fromScopeWithName:withResolver:withRejecter:)
116+
func collection_RemoveChangeListener(
117+
changeListenerToken: NSString,
118+
collectionName: NSString,
119+
name: NSString,
120+
scopeName: NSString,
121+
resolve: @escaping RCTPromiseResolveBlock,
122+
reject: @escaping RCTPromiseRejectBlock
123+
) -> Void {
124+
let token = String(changeListenerToken)
125+
126+
backgroundQueue.async {
127+
if let listener = self.collectionChangeListeners[token] as? ListenerToken {
128+
// Remove the listener
129+
listener.remove()
130+
self.collectionChangeListeners.removeValue(forKey: token)
131+
resolve(nil)
132+
} else {
133+
reject("DATABASE_ERROR", "No listener found for token \(token)", nil)
134+
}
135+
}
136+
}
137+
60138

61139
@objc(collection_CreateCollection:fromDatabaseWithName:fromScopeWithName:withResolver:withRejecter:)
62140
func collection_CreateCollection(

ios/cbl-js-swift

src/CblReactNativeEngine.tsx

Lines changed: 79 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ export class CblReactNativeEngine implements ICoreEngine {
7878
new Map();
7979
private _isReplicatorDocumentChangeEventSetup: boolean = false;
8080

81+
private _collectionChangeListeners: Map<string, ListenerCallback> = new Map();
82+
8183
private static readonly LINKING_ERROR =
8284
`The package 'cbl-reactnative' doesn't seem to be linked. Make sure: \n\n` +
8385
Platform.select({ ios: "- You have run 'pod install'\n", default: '' }) +
@@ -133,12 +135,45 @@ export class CblReactNativeEngine implements ICoreEngine {
133135
};
134136

135137
collection_AddChangeListener(
136-
// eslint-disable-next-line
137138
args: CollectionChangeListenerArgs,
138-
// eslint-disable-next-line
139139
lcb: ListenerCallback
140140
): Promise<void> {
141-
return Promise.resolve(undefined);
141+
return new Promise((resolve, reject) => {
142+
const token = args.changeListenerToken;
143+
const subscriptionKey = `${args.name}.${args.scopeName}.${args.collectionName}_${token}`;
144+
145+
if (this._collectionChangeListeners.has(token)) {
146+
reject(new Error('Change listener token already exists'));
147+
return;
148+
}
149+
150+
const subscription = this.startListeningEvents(
151+
this._eventCollectionChange,
152+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
153+
(results: any) => {
154+
if (results.token === token) {
155+
this.debugLog(
156+
`::DEBUG:: Received collection change event for token: ${token}`
157+
);
158+
lcb(results);
159+
}
160+
}
161+
);
162+
163+
this._emitterSubscriptions.set(subscriptionKey, subscription);
164+
this._collectionChangeListeners.set(token, lcb);
165+
166+
this.CblReactNative.collection_AddChangeListener(
167+
token,
168+
args.collectionName,
169+
args.name,
170+
args.scopeName
171+
).then(
172+
() => resolve(),
173+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
174+
(error: any) => reject(error)
175+
);
176+
});
142177
}
143178

144179
// eslint-disable-next-line
@@ -442,7 +477,47 @@ export class CblReactNativeEngine implements ICoreEngine {
442477
// eslint-disable-next-line
443478
args: CollectionChangeListenerArgs
444479
): Promise<void> {
445-
return Promise.resolve(undefined);
480+
return new Promise((resolve, reject) => {
481+
const token = args.changeListenerToken;
482+
const collectionId = `${args.name}.${args.scopeName}.${args.collectionName}`;
483+
const subscriptionKey = `${collectionId}_${token}`;
484+
485+
// Remove the subscription
486+
if (this._emitterSubscriptions.has(subscriptionKey)) {
487+
this._emitterSubscriptions.get(subscriptionKey)?.remove();
488+
this._emitterSubscriptions.delete(subscriptionKey);
489+
}
490+
491+
// Remove the listener from the collection listeners map
492+
if (this._collectionChangeListeners.has(token)) {
493+
this._collectionChangeListeners.delete(token);
494+
} else {
495+
reject(new Error(`No listener found with token: ${token}`));
496+
return;
497+
}
498+
499+
// 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(
506+
() => {
507+
this.debugLog(
508+
`::DEBUG:: collection_RemoveChangeListener completed for token: ${token}`
509+
);
510+
resolve();
511+
},
512+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
513+
(error: any) => {
514+
this.debugLog(
515+
`::DEBUG:: collection_RemoveChangeListener Error: ${error}`
516+
);
517+
reject(error);
518+
}
519+
);
520+
});
446521
}
447522

448523
collection_RemoveDocumentChangeListener(

0 commit comments

Comments
 (0)