Skip to content

Commit 45168e0

Browse files
author
Wojtach
committed
feat: added live query listener
1 parent 4a237ce commit 45168e0

File tree

3 files changed

+164
-5
lines changed

3 files changed

+164
-5
lines changed

ios/CblReactnative.mm

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,19 @@ @interface RCT_EXTERN_MODULE(CblReactnative, RCTEventEmitter)
236236

237237
// MARK: - SQL++ Query Functions
238238

239+
RCT_EXTERN_METHOD(query_AddChangeListener:
240+
(NSString *)changeListenerToken
241+
withQuery:(NSString *)query
242+
withParameters:(NSDictionary *)parameters
243+
fromDatabaseWithName:(NSString *)name
244+
withResolver:(RCTPromiseResolveBlock)resolve
245+
withRejecter:(RCTPromiseRejectBlock)reject)
246+
247+
RCT_EXTERN_METHOD(query_RemoveChangeListener:
248+
(NSString *)changeListenerToken
249+
withResolver:(RCTPromiseResolveBlock)resolve
250+
withRejecter:(RCTPromiseRejectBlock)reject)
251+
239252
RCT_EXTERN_METHOD(query_Execute:
240253
(NSString *)query
241254
withParameters: (NSDictionary *)parameters

ios/CblReactnative.swift

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1041,6 +1041,87 @@ class CblReactnative: RCTEventEmitter {
10411041
}
10421042

10431043
// MARK: - SQL++ Query Functions
1044+
@objc(query_AddChangeListener:withQuery:withParameters:fromDatabaseWithName:withResolver:withRejecter:)
1045+
func query_AddChangeListener(
1046+
changeListenerToken: NSString,
1047+
query: NSString,
1048+
parameters: NSDictionary,
1049+
name: NSString,
1050+
resolve: @escaping RCTPromiseResolveBlock,
1051+
reject: @escaping RCTPromiseRejectBlock
1052+
) -> Void {
1053+
let (isError, databaseName) = DataAdapter.shared.adaptDatabaseName(name: name, reject: reject)
1054+
let (isTokenError, token) = DataAdapter.shared.adaptNonEmptyString(value: changeListenerToken, propertyName: "changeListenerToken", reject: reject)
1055+
let (isQueryError, queryString) = DataAdapter.shared.adaptNonEmptyString(value: query, propertyName: "query", reject: reject)
1056+
1057+
if isError || isTokenError || isQueryError {
1058+
return
1059+
}
1060+
1061+
backgroundQueue.async {
1062+
do {
1063+
guard let database = DatabaseManager.shared.getDatabase(databaseName) else {
1064+
reject("DATABASE_ERROR", "Could not find database with name \(databaseName)", nil)
1065+
return
1066+
}
1067+
1068+
let query = try database.createQuery(queryString)
1069+
1070+
if parameters.count > 0 {
1071+
let params = try QueryHelper.getParamatersFromJson(parameters as? [String: Any] ?? [:])
1072+
query.parameters = params
1073+
}
1074+
1075+
let listener = query.addChangeListener(withQueue: self.backgroundQueue) { [weak self] (change) in
1076+
guard let self = self else { return }
1077+
1078+
let resultData = NSMutableDictionary()
1079+
resultData.setValue(token, forKey: "token")
1080+
1081+
if let results = change.results {
1082+
// Convert results to JSON format
1083+
let resultJSONs = results.map { $0.toJSON() }
1084+
let jsonArray = "[" + resultJSONs.joined(separator: ",") + "]"
1085+
resultData.setValue(jsonArray, forKey: "data")
1086+
}
1087+
1088+
if let error = change.error {
1089+
resultData.setValue(error.localizedDescription, forKey: "error")
1090+
}
1091+
1092+
self.sendEvent(withName: self.kQueryChange, body: resultData)
1093+
}
1094+
1095+
self.queryChangeListeners[token] = listener
1096+
resolve(nil)
1097+
} catch let error as NSError {
1098+
reject("DATABASE_ERROR", error.localizedDescription, nil)
1099+
} catch {
1100+
reject("DATABASE_ERROR", error.localizedDescription, nil)
1101+
}
1102+
}
1103+
}
1104+
1105+
@objc(query_RemoveChangeListener:withResolver:withRejecter:)
1106+
func query_RemoveChangeListener(
1107+
changeListenerToken: NSString,
1108+
resolve: @escaping RCTPromiseResolveBlock,
1109+
reject: @escaping RCTPromiseRejectBlock
1110+
) -> Void {
1111+
let token = String(changeListenerToken)
1112+
1113+
backgroundQueue.async {
1114+
if let listener = self.queryChangeListeners[token] as? ListenerToken {
1115+
listener.remove()
1116+
self.queryChangeListeners.removeValue(forKey: token)
1117+
resolve(nil)
1118+
return
1119+
}
1120+
1121+
reject("DATABASE_ERROR", "No query listener found for token \(token)", nil)
1122+
}
1123+
}
1124+
10441125
@objc(query_Execute:
10451126
withParameters:
10461127
fromDatabaseWithName:

src/CblReactNativeEngine.tsx

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ export class CblReactNativeEngine implements ICoreEngine {
7979
private _isReplicatorDocumentChangeEventSetup: boolean = false;
8080

8181
private _collectionChangeListeners: Map<string, ListenerCallback> = new Map();
82+
private _queryChangeListeners: Map<string, ListenerCallback> = new Map();
8283

8384
private static readonly LINKING_ERROR =
8485
`The package 'cbl-reactnative' doesn't seem to be linked. Make sure: \n\n` +
@@ -993,12 +994,49 @@ export class CblReactNativeEngine implements ICoreEngine {
993994
}
994995

995996
query_AddChangeListener(
996-
// eslint-disable-next-line
997997
args: QueryChangeListenerArgs,
998-
// eslint-disable-next-line
999998
lcb: ListenerCallback
1000999
): Promise<void> {
1001-
return Promise.resolve(undefined);
1000+
return new Promise((resolve, reject) => {
1001+
const token = args.changeListenerToken;
1002+
1003+
if (this._queryChangeListeners.has(token)) {
1004+
reject(new Error('Query change listener token already exists'));
1005+
return;
1006+
}
1007+
1008+
const subscription = this.startListeningEvents(
1009+
this._eventQueryChange,
1010+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
1011+
(results: any) => {
1012+
if (results.token === token) {
1013+
this.debugLog(
1014+
`::DEBUG:: Received query change event for token: ${token}`
1015+
);
1016+
lcb(results);
1017+
}
1018+
}
1019+
);
1020+
1021+
this._emitterSubscriptions.set(token, subscription);
1022+
this._queryChangeListeners.set(token, lcb);
1023+
1024+
this.CblReactNative.query_AddChangeListener(
1025+
token,
1026+
args.query,
1027+
args.parameters,
1028+
args.name
1029+
).then(
1030+
() => resolve(),
1031+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
1032+
(error: any) => {
1033+
this._emitterSubscriptions.delete(token);
1034+
this._queryChangeListeners.delete(token);
1035+
subscription.remove();
1036+
reject(error);
1037+
}
1038+
);
1039+
});
10021040
}
10031041

10041042
query_Execute(args: QueryExecuteArgs): Promise<Result> {
@@ -1038,10 +1076,37 @@ export class CblReactNativeEngine implements ICoreEngine {
10381076
}
10391077

10401078
query_RemoveChangeListener(
1041-
// eslint-disable-next-line
10421079
args: QueryRemoveChangeListenerArgs
10431080
): Promise<void> {
1044-
return Promise.resolve(undefined);
1081+
return new Promise((resolve, reject) => {
1082+
const token = args.changeListenerToken;
1083+
1084+
if (this._emitterSubscriptions.has(token)) {
1085+
this._emitterSubscriptions.get(token)?.remove();
1086+
this._emitterSubscriptions.delete(token);
1087+
}
1088+
1089+
if (this._queryChangeListeners.has(token)) {
1090+
this._queryChangeListeners.delete(token);
1091+
} else {
1092+
reject(new Error(`No query listener found with token: ${token}`));
1093+
return;
1094+
}
1095+
1096+
this.CblReactNative.query_RemoveChangeListener(token).then(
1097+
() => {
1098+
this.debugLog(
1099+
`::DEBUG:: query_RemoveChangeListener completed for token: ${token}`
1100+
);
1101+
resolve();
1102+
},
1103+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
1104+
(error: any) => {
1105+
this.debugLog(`::DEBUG:: query_RemoveChangeListener Error: ${error}`);
1106+
reject(error);
1107+
}
1108+
);
1109+
});
10451110
}
10461111

10471112
replicator_AddChangeListener(

0 commit comments

Comments
 (0)