Skip to content

Commit f68babe

Browse files
Merge pull request #51 from Wojtach/feat/50-android-replicator-listeners
feat: added handling replicator listeners for android
2 parents c0acc87 + 2b13dc0 commit f68babe

File tree

7 files changed

+146
-81
lines changed

7 files changed

+146
-81
lines changed

android/src/main/java/com/cblreactnative/CblReactnativeModule.kt

Lines changed: 118 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import cbl.js.kotiln.CollectionManager
55
import cbl.js.kotiln.FileSystemHelper
66
import cbl.js.kotiln.LoggingManager
77
import cbl.js.kotiln.ReplicatorManager
8+
import cbl.js.kotiln.ReplicatorHelper
89
import com.couchbase.lite.*
910
import com.facebook.react.bridge.Promise
1011
import com.facebook.react.bridge.ReactApplicationContext
@@ -39,7 +40,7 @@ class CblReactnativeModule(reactContext: ReactApplicationContext) :
3940
return NAME
4041
}
4142

42-
/*
43+
4344
private fun sendEvent(
4445
reactContext: ReactContext,
4546
eventName: String,
@@ -48,7 +49,7 @@ class CblReactnativeModule(reactContext: ReactApplicationContext) :
4849
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
4950
.emit(eventName, params)
5051
}
51-
*/
52+
5253

5354
// Collection Functions
5455
@ReactMethod
@@ -926,36 +927,74 @@ class CblReactnativeModule(reactContext: ReactApplicationContext) :
926927
}
927928

928929
// Replicator Functions
929-
@ReactMethod
930-
fun replicator_AddChangeListener(
931-
changeListenerToken: String,
932-
replicatorId: String,
933-
promise: Promise){
934-
GlobalScope.launch(Dispatchers.IO) {
935-
try {
936-
if (!DataValidation.validateReplicatorId(replicatorId, promise)){
937-
return@launch
938-
}
939-
val replicator = ReplicatorManager.getReplicator(replicatorId)
940-
val listener = replicator?.addChangeListener { change ->
941-
val map = DataAdapter.replicatorStatusToMap(change.status)
942-
context.runOnUiQueueThread {
943-
//sendEvent(context, "replicatorStatusChange", map)
944-
}
945-
}
946-
listener?.let {
947-
replicatorChangeListeners[changeListenerToken] = it
948-
}
930+
@ReactMethod
931+
fun replicator_AddChangeListener(
932+
changeListenerToken: String,
933+
replicatorId: String,
934+
promise: Promise){
935+
GlobalScope.launch(Dispatchers.IO) {
936+
try {
937+
if (!DataValidation.validateReplicatorId(replicatorId, promise)){
938+
return@launch
939+
}
940+
val replicator = ReplicatorManager.getReplicator(replicatorId)
941+
val listener = replicator?.addChangeListener { change ->
942+
val statusMap = ReplicatorHelper.generateReplicatorStatusMap(change.status)
943+
val resultMap = Arguments.createMap()
944+
resultMap.putString("token", changeListenerToken)
945+
resultMap.putMap("status", statusMap)
949946
context.runOnUiQueueThread {
950-
promise.resolve(null)
947+
sendEvent(context, "replicatorStatusChange", resultMap)
951948
}
952-
} catch (e: Throwable) {
949+
}
950+
listener?.let {
951+
replicatorChangeListeners[changeListenerToken] = it
952+
}
953+
context.runOnUiQueueThread {
954+
promise.resolve(null)
955+
}
956+
} catch (e: Throwable) {
957+
context.runOnUiQueueThread {
958+
promise.reject("REPLICATOR_ERROR", e.message)
959+
}
960+
}
961+
}
962+
}
963+
964+
@ReactMethod
965+
fun replicator_AddDocumentChangeListener(
966+
changeListenerToken: String,
967+
replicatorId: String,
968+
promise: Promise){
969+
GlobalScope.launch(Dispatchers.IO) {
970+
try {
971+
if (!DataValidation.validateReplicatorId(replicatorId, promise)){
972+
return@launch
973+
}
974+
val replicator = ReplicatorManager.getReplicator(replicatorId)
975+
val listener = replicator?.addDocumentReplicationListener { change ->
976+
val documentMap = ReplicatorHelper.generateDocumentReplicationMap(change.documents, change.isPush)
977+
val resultMap = Arguments.createMap()
978+
resultMap.putString("token", changeListenerToken)
979+
resultMap.putMap("documents", documentMap)
953980
context.runOnUiQueueThread {
954-
promise.reject("REPLICATOR_ERROR", e.message)
981+
sendEvent(context, "replicatorDocumentChange", resultMap)
955982
}
956983
}
984+
listener?.let {
985+
replicatorDocumentListeners[changeListenerToken] = it
986+
}
987+
context.runOnUiQueueThread {
988+
promise.resolve(null)
989+
}
990+
} catch (e: Throwable) {
991+
context.runOnUiQueueThread {
992+
promise.reject("REPLICATOR_ERROR", e.message)
993+
}
957994
}
958995
}
996+
}
997+
959998
@ReactMethod
960999
fun replicator_Cleanup(
9611000
replicatorId: String,
@@ -977,26 +1016,27 @@ class CblReactnativeModule(reactContext: ReactApplicationContext) :
9771016
}
9781017
}
9791018

980-
@ReactMethod
981-
fun replicator_Create(
982-
config: ReadableMap,
983-
promise: Promise) {
984-
GlobalScope.launch(Dispatchers.IO) {
985-
try {
986-
val replicatorConfig = DataAdapter.readableMapToReplicatorConfig(config)
987-
val replicatorId = ReplicatorManager.createReplicator(replicatorConfig)
988-
val map = Arguments.createMap()
989-
map.putString("replicatorId", replicatorId)
990-
context.runOnUiQueueThread {
991-
promise.resolve(map)
992-
}
993-
} catch (e: Throwable) {
994-
context.runOnUiQueueThread {
995-
promise.reject("REPLICATOR_ERROR", e.message)
996-
}
1019+
@ReactMethod
1020+
fun replicator_Create(
1021+
config: ReadableMap,
1022+
promise: Promise) {
1023+
GlobalScope.launch(Dispatchers.IO) {
1024+
try {
1025+
// Use the ReplicatorHelper to create a configuration from the ReadableMap
1026+
val replicatorConfig = ReplicatorHelper.replicatorConfigFromJson(config)
1027+
val replicatorId = ReplicatorManager.createReplicator(replicatorConfig)
1028+
val map = Arguments.createMap()
1029+
map.putString("replicatorId", replicatorId)
1030+
context.runOnUiQueueThread {
1031+
promise.resolve(map)
1032+
}
1033+
} catch (e: Throwable) {
1034+
context.runOnUiQueueThread {
1035+
promise.reject("REPLICATOR_ERROR", e.message)
9971036
}
9981037
}
9991038
}
1039+
}
10001040

10011041
@ReactMethod
10021042
fun replicator_GetPendingDocumentIds(
@@ -1077,31 +1117,50 @@ class CblReactnativeModule(reactContext: ReactApplicationContext) :
10771117
}
10781118
}
10791119

1080-
@ReactMethod
1081-
fun replicator_RemoveChangeListener(
1082-
changeListenerToken: String,
1083-
replicatorId: String,
1084-
promise: Promise) {
1085-
GlobalScope.launch(Dispatchers.IO) {
1086-
try {
1087-
if (!DataValidation.validateReplicatorId(replicatorId, promise)){
1088-
return@launch
1089-
}
1090-
val changeListener = replicatorChangeListeners[changeListenerToken]
1091-
changeListener?.let {
1092-
changeListener.remove()
1093-
replicatorChangeListeners.remove(changeListenerToken)
1094-
}
1120+
@ReactMethod
1121+
fun replicator_RemoveChangeListener(
1122+
changeListenerToken: String,
1123+
replicatorId: String,
1124+
promise: Promise) {
1125+
GlobalScope.launch(Dispatchers.IO) {
1126+
try {
1127+
if (!DataValidation.validateReplicatorId(replicatorId, promise)){
1128+
return@launch
1129+
}
1130+
1131+
// Check for replicator change listeners
1132+
if (replicatorChangeListeners.containsKey(changeListenerToken)) {
1133+
val listener = replicatorChangeListeners[changeListenerToken]
1134+
listener?.remove()
1135+
replicatorChangeListeners.remove(changeListenerToken)
10951136
context.runOnUiQueueThread {
10961137
promise.resolve(null)
10971138
}
1098-
} catch (e: Throwable) {
1139+
return@launch
1140+
}
1141+
1142+
// Check for document change listeners
1143+
if (replicatorDocumentListeners.containsKey(changeListenerToken)) {
1144+
val listener = replicatorDocumentListeners[changeListenerToken]
1145+
listener?.remove()
1146+
replicatorDocumentListeners.remove(changeListenerToken)
10991147
context.runOnUiQueueThread {
1100-
promise.reject("REPLICATOR_ERROR", e.message)
1148+
promise.resolve(null)
11011149
}
1150+
return@launch
1151+
}
1152+
1153+
// If no listener found
1154+
context.runOnUiQueueThread {
1155+
promise.reject("REPLICATOR_ERROR", "No such listener found with token $changeListenerToken")
1156+
}
1157+
} catch (e: Throwable) {
1158+
context.runOnUiQueueThread {
1159+
promise.reject("REPLICATOR_ERROR", e.message)
11021160
}
11031161
}
11041162
}
1163+
}
11051164

11061165
@ReactMethod
11071166
fun replicator_ResetCheckpoint(

expo-example/app/tests/replication.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React from 'react';
22
import TestRunnerContainer from '@/components/TestRunnerContainer/TestRunnerContainer';
33

4-
import { ReplicatorTests } from "../../cblite-js-tests/cblite-tests/e2e/replicator-test";
4+
import { ReplicatorTests } from '../../cblite-js-tests/cblite-tests/e2e/replicator-test';
55

66
export default function TestsReplicatorScreen() {
77
function reset() {}
@@ -18,7 +18,7 @@ export default function TestsReplicatorScreen() {
1818
return (
1919
<TestRunnerContainer
2020
navigationTitle="Replicator Tests"
21-
collapseTitle="Replicator Tests"
21+
subTitle="Run Sync Gate before tests - visit tests README.md"
2222
testCases={[ReplicatorTests]}
2323
/>
2424
);

expo-example/components/TestRunnerContainer/TestRunnerContainer.tsx

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import TestCurrentRunningView from '@/components/TestCurrentRunningView/TestCurr
1111
import TestResultItem from '@/components/TestResultItem/TestResultItem';
1212
import { FlatList } from '@gluestack-ui/themed';
1313
import { ITestResult, TestCase } from '../../cblite-js-tests/cblite-tests/e2e';
14+
import { Text } from 'react-native';
1415

1516
const MemoizedTestToolbarHeaderView = memo(TestToolbarHeaderView);
1617
const MemoizedTestCurrentRunningView = memo(TestCurrentRunningView);
@@ -19,13 +20,13 @@ const MemoizedTestResultItem = memo(TestResultItem);
1920

2021
interface ContainerProps<T extends new () => TestCase> {
2122
navigationTitle: string;
22-
collapseTitle: string;
23+
subTitle: string;
2324
testCases: T[];
2425
}
2526

2627
function TestRunnerContainer<T extends new () => TestCase>({
2728
navigationTitle,
28-
collapseTitle,
29+
subTitle,
2930
testCases,
3031
}: ContainerProps<T>) {
3132
const styles = useStyleScheme();
@@ -41,23 +42,26 @@ function TestRunnerContainer<T extends new () => TestCase>({
4142
failedCount,
4243
runTests,
4344
reset,
44-
isRunning
45+
isRunning,
4546
} = useTestRunner(testCases);
4647

4748
useNavigationBarTitleResetOption(navigationTitle, navigation, reset);
4849

49-
const icons = React.useMemo(() => [
50-
{
51-
iconName: 'stop',
52-
onPress: () => setShouldCancel(true),
53-
disabled: !isRunning,
54-
},
55-
{
56-
iconName: 'play',
57-
onPress: runTests,
58-
disabled: isRunning,
59-
},
60-
], [isRunning, setShouldCancel, runTests]);
50+
const icons = React.useMemo(
51+
() => [
52+
{
53+
iconName: 'stop',
54+
onPress: () => setShouldCancel(true),
55+
disabled: !isRunning,
56+
},
57+
{
58+
iconName: 'play',
59+
onPress: runTests,
60+
disabled: isRunning,
61+
},
62+
],
63+
[isRunning, setShouldCancel, runTests]
64+
);
6165

6266
return (
6367
<SafeAreaView style={styles.container}>
@@ -67,7 +71,8 @@ function TestRunnerContainer<T extends new () => TestCase>({
6771
icons={icons}
6872
style={localStyles}
6973
/>
70-
{ isRunning && (
74+
<Text style={{ paddingTop: 10, paddingLeft: 15 }}>{subTitle}</Text>
75+
{isRunning && (
7176
<MemoizedTestCurrentRunningView
7277
currentTestName={currentMessage?.testName || ''}
7378
style={localStyles}
@@ -104,4 +109,4 @@ const localStyles = StyleSheet.create({
104109
},
105110
});
106111

107-
export default memo(TestRunnerContainer);
112+
export default memo(TestRunnerContainer);

src/CblReactNativeEngine.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ export class CblReactNativeEngine implements ICoreEngine {
6161
_defaultCollectionName = '_default';
6262
_defaultScopeName = '_default';
6363
debugConsole = false;
64+
platform = Platform.OS;
6465

6566
//event name mapping for the native side of the module
6667

0 commit comments

Comments
 (0)