Skip to content

Commit 3ec94a4

Browse files
authored
Add support for Keypath filtering in notifications (#1547)
1 parent 882d432 commit 3ec94a4

40 files changed

+1987
-76
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
* None.
55

66
### Enhancements
7+
* Added support for keypaths in `asFlow()` methods on objects and queries. This makes it possible to control which properties will trigger change events, including properties on objects below the default nested limit of 4. (Issue [#661](https://github.com/realm/realm-kotlin/issues/661))
78
* Support for experimental K2-compilation with `kotlin.experimental.tryK2=true`. (Issue [#1483](https://github.com/realm/realm-kotlin/issues/1483))
89
* [Sync] Added support for multiplexing sync connections. When enabled, a single
910
connection is used per sync user rather than one per synchronized Realm. This
@@ -32,7 +33,7 @@
3233
* Minimum R8: 8.0.34.
3334

3435
### Internal
35-
* None.
36+
* Updated to Realm Core 13.24.0, commit e593a5f19d0dc205db931ec5618a8c10c95cac90.
3637

3738

3839
## 1.12.1-SNAPSHOT (YYYY-MM-DD)

packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ interface RealmCallbackTokenT : CapiT
6969
interface RealmNotificationTokenT : CapiT
7070
interface RealmChangesT : CapiT
7171
interface RealmSchedulerT : CapiT
72+
interface RealmKeyPathArrayT : CapiT
7273

7374
// Public type aliases binding to internal verbose type safe type definitions. This should allow us
7475
// to easily change implementation details later on.
@@ -88,6 +89,7 @@ typealias RealmCallbackTokenPointer = NativePointer<RealmCallbackTokenT>
8889
typealias RealmNotificationTokenPointer = NativePointer<RealmNotificationTokenT>
8990
typealias RealmChangesPointer = NativePointer<RealmChangesT>
9091
typealias RealmSchedulerPointer = NativePointer<RealmSchedulerT>
92+
typealias RealmKeyPathArrayPointer = NativePointer<RealmKeyPathArrayT>
9193

9294
// Sync types
9395
// Pure marker interfaces corresponding to the C-API realm_x_t struct types
@@ -435,24 +437,30 @@ expect object RealmInterop {
435437
): RealmObjectPointer?
436438
fun realm_object_delete(obj: RealmObjectPointer)
437439

440+
fun realm_create_key_paths_array(realm: RealmPointer, clazz: ClassKey, keyPaths: List<String>): RealmKeyPathArrayPointer
438441
fun realm_object_add_notification_callback(
439442
obj: RealmObjectPointer,
443+
keyPaths: RealmKeyPathArrayPointer?,
440444
callback: Callback<RealmChangesPointer>
441445
): RealmNotificationTokenPointer
442446
fun realm_results_add_notification_callback(
443447
results: RealmResultsPointer,
448+
keyPaths: RealmKeyPathArrayPointer?,
444449
callback: Callback<RealmChangesPointer>
445450
): RealmNotificationTokenPointer
446451
fun realm_list_add_notification_callback(
447452
list: RealmListPointer,
453+
keyPaths: RealmKeyPathArrayPointer?,
448454
callback: Callback<RealmChangesPointer>
449455
): RealmNotificationTokenPointer
450456
fun realm_set_add_notification_callback(
451457
set: RealmSetPointer,
458+
keyPaths: RealmKeyPathArrayPointer?,
452459
callback: Callback<RealmChangesPointer>
453460
): RealmNotificationTokenPointer
454461
fun realm_dictionary_add_notification_callback(
455462
map: RealmMapPointer,
463+
keyPaths: RealmKeyPathArrayPointer?,
456464
callback: Callback<RealmChangesPointer>
457465
): RealmNotificationTokenPointer
458466
fun realm_object_changes_get_modified_properties(

packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ import org.mongodb.kbson.ObjectId
4141
actual val INVALID_CLASS_KEY: ClassKey by lazy { ClassKey(realmc.getRLM_INVALID_CLASS_KEY()) }
4242
actual val INVALID_PROPERTY_KEY: PropertyKey by lazy { PropertyKey(realmc.getRLM_INVALID_PROPERTY_KEY()) }
4343

44+
// The value to pass to JNI functions that accept longs as replacements for pointers and need
45+
// to represent null.
46+
const val NULL_POINTER_VALUE = 0L
47+
4448
/**
4549
* JVM/Android interop implementation.
4650
*
@@ -813,14 +817,22 @@ actual object RealmInterop {
813817
return realmc.realm_dictionary_is_valid(dictionary.cptr())
814818
}
815819

820+
actual fun realm_create_key_paths_array(realm: RealmPointer, clazz: ClassKey, keyPaths: List<String>): RealmKeyPathArrayPointer {
821+
val ptr = realmc.realm_create_key_path_array(realm.cptr(), clazz.key, keyPaths.size.toLong(), keyPaths.toTypedArray())
822+
return LongPointerWrapper(ptr)
823+
}
824+
816825
actual fun realm_object_add_notification_callback(
817826
obj: RealmObjectPointer,
827+
keyPaths: RealmKeyPathArrayPointer?,
818828
callback: Callback<RealmChangesPointer>
819829
): RealmNotificationTokenPointer {
830+
820831
return LongPointerWrapper(
821832
realmc.register_notification_cb(
822833
obj.cptr(),
823834
CollectionType.RLM_COLLECTION_TYPE_NONE.nativeValue,
835+
keyPaths?.cptr() ?: NULL_POINTER_VALUE,
824836
object : NotificationCallback {
825837
override fun onChange(pointer: Long) {
826838
callback.onChange(LongPointerWrapper(realmc.realm_clone(pointer), true))
@@ -833,11 +845,13 @@ actual object RealmInterop {
833845

834846
actual fun realm_results_add_notification_callback(
835847
results: RealmResultsPointer,
848+
keyPaths: RealmKeyPathArrayPointer?,
836849
callback: Callback<RealmChangesPointer>
837850
): RealmNotificationTokenPointer {
838851
return LongPointerWrapper(
839852
realmc.register_results_notification_cb(
840853
results.cptr(),
854+
keyPaths?.cptr() ?: NULL_POINTER_VALUE,
841855
object : NotificationCallback {
842856
override fun onChange(pointer: Long) {
843857
callback.onChange(LongPointerWrapper(realmc.realm_clone(pointer), true))
@@ -850,12 +864,14 @@ actual object RealmInterop {
850864

851865
actual fun realm_list_add_notification_callback(
852866
list: RealmListPointer,
867+
keyPaths: RealmKeyPathArrayPointer?,
853868
callback: Callback<RealmChangesPointer>
854869
): RealmNotificationTokenPointer {
855870
return LongPointerWrapper(
856871
realmc.register_notification_cb(
857872
list.cptr(),
858873
CollectionType.RLM_COLLECTION_TYPE_LIST.nativeValue,
874+
keyPaths?.cptr() ?: NULL_POINTER_VALUE,
859875
object : NotificationCallback {
860876
override fun onChange(pointer: Long) {
861877
callback.onChange(LongPointerWrapper(realmc.realm_clone(pointer), true))
@@ -868,12 +884,14 @@ actual object RealmInterop {
868884

869885
actual fun realm_set_add_notification_callback(
870886
set: RealmSetPointer,
887+
keyPaths: RealmKeyPathArrayPointer?,
871888
callback: Callback<RealmChangesPointer>
872889
): RealmNotificationTokenPointer {
873890
return LongPointerWrapper(
874891
realmc.register_notification_cb(
875892
set.cptr(),
876893
CollectionType.RLM_COLLECTION_TYPE_SET.nativeValue,
894+
keyPaths?.cptr() ?: NULL_POINTER_VALUE,
877895
object : NotificationCallback {
878896
override fun onChange(pointer: Long) {
879897
callback.onChange(LongPointerWrapper(realmc.realm_clone(pointer), true))
@@ -886,12 +904,14 @@ actual object RealmInterop {
886904

887905
actual fun realm_dictionary_add_notification_callback(
888906
map: RealmMapPointer,
907+
keyPaths: RealmKeyPathArrayPointer?,
889908
callback: Callback<RealmChangesPointer>
890909
): RealmNotificationTokenPointer {
891910
return LongPointerWrapper(
892911
realmc.register_notification_cb(
893912
map.cptr(),
894913
CollectionType.RLM_COLLECTION_TYPE_DICTIONARY.nativeValue,
914+
keyPaths?.cptr() ?: NULL_POINTER_VALUE,
895915
object : NotificationCallback {
896916
override fun onChange(pointer: Long) {
897917
callback.onChange(LongPointerWrapper(realmc.realm_clone(pointer), true))

packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import kotlinx.cinterop.COpaquePointer
4545
import kotlinx.cinterop.CPointed
4646
import kotlinx.cinterop.CPointer
4747
import kotlinx.cinterop.CPointerVar
48+
import kotlinx.cinterop.CPointerVarOf
4849
import kotlinx.cinterop.CValue
4950
import kotlinx.cinterop.CVariable
5051
import kotlinx.cinterop.LongVar
@@ -70,6 +71,7 @@ import kotlinx.cinterop.readValue
7071
import kotlinx.cinterop.refTo
7172
import kotlinx.cinterop.set
7273
import kotlinx.cinterop.staticCFunction
74+
import kotlinx.cinterop.toCStringArray
7375
import kotlinx.cinterop.toKString
7476
import kotlinx.cinterop.useContents
7577
import kotlinx.cinterop.usePinned
@@ -1653,8 +1655,17 @@ actual object RealmInterop {
16531655
checkedBooleanResult(realm_wrapper.realm_object_delete(obj.cptr()))
16541656
}
16551657

1658+
actual fun realm_create_key_paths_array(realm: RealmPointer, clazz: ClassKey, keyPaths: List<String>): RealmKeyPathArrayPointer {
1659+
memScoped {
1660+
val userKeyPaths: CPointer<CPointerVarOf<CPointer<ByteVarOf<Byte>>>> = keyPaths.toCStringArray(this)
1661+
val keyPathPointer = realm_wrapper.realm_create_key_path_array(realm.cptr(), clazz.key.toUInt(), keyPaths.size.toULong(), userKeyPaths)
1662+
return CPointerWrapper(keyPathPointer)
1663+
}
1664+
}
1665+
16561666
actual fun realm_object_add_notification_callback(
16571667
obj: RealmObjectPointer,
1668+
keyPaths: RealmKeyPathArrayPointer?,
16581669
callback: Callback<RealmChangesPointer>
16591670
): RealmNotificationTokenPointer {
16601671
return CPointerWrapper(
@@ -1667,7 +1678,7 @@ actual object RealmInterop {
16671678
?.dispose()
16681679
?: error("Notification callback data should never be null")
16691680
},
1670-
null, // See https://github.com/realm/realm-kotlin/issues/661
1681+
keyPaths?.cptr(),
16711682
staticCFunction { userdata, change -> // Change callback
16721683
try {
16731684
userdata?.asStableRef<Callback<RealmChangesPointer>>()
@@ -1688,6 +1699,7 @@ actual object RealmInterop {
16881699

16891700
actual fun realm_results_add_notification_callback(
16901701
results: RealmResultsPointer,
1702+
keyPaths: RealmKeyPathArrayPointer?,
16911703
callback: Callback<RealmChangesPointer>
16921704
): RealmNotificationTokenPointer {
16931705
return CPointerWrapper(
@@ -1700,7 +1712,7 @@ actual object RealmInterop {
17001712
?.dispose()
17011713
?: error("Notification callback data should never be null")
17021714
},
1703-
null, // See https://github.com/realm/realm-kotlin/issues/661
1715+
keyPaths?.cptr(),
17041716
staticCFunction { userdata, change -> // Change callback
17051717
try {
17061718
userdata?.asStableRef<Callback<RealmChangesPointer>>()
@@ -1721,6 +1733,7 @@ actual object RealmInterop {
17211733

17221734
actual fun realm_list_add_notification_callback(
17231735
list: RealmListPointer,
1736+
keyPaths: RealmKeyPathArrayPointer?,
17241737
callback: Callback<RealmChangesPointer>
17251738
): RealmNotificationTokenPointer {
17261739
return CPointerWrapper(
@@ -1732,7 +1745,7 @@ actual object RealmInterop {
17321745
userdata?.asStableRef<Callback<RealmChangesPointer>>()?.dispose()
17331746
?: error("Notification callback data should never be null")
17341747
},
1735-
null, // See https://github.com/realm/realm-kotlin/issues/661
1748+
keyPaths?.cptr(),
17361749
staticCFunction { userdata, change -> // Change callback
17371750
try {
17381751
userdata?.asStableRef<Callback<RealmChangesPointer>>()
@@ -1753,6 +1766,7 @@ actual object RealmInterop {
17531766

17541767
actual fun realm_set_add_notification_callback(
17551768
set: RealmSetPointer,
1769+
keyPaths: RealmKeyPathArrayPointer?,
17561770
callback: Callback<RealmChangesPointer>
17571771
): RealmNotificationTokenPointer {
17581772
return CPointerWrapper(
@@ -1765,7 +1779,7 @@ actual object RealmInterop {
17651779
?.dispose()
17661780
?: error("Notification callback data should never be null")
17671781
},
1768-
null, // See https://github.com/realm/realm-kotlin/issues/661
1782+
keyPaths?.cptr(),
17691783
staticCFunction { userdata, change -> // Change callback
17701784
try {
17711785
userdata?.asStableRef<Callback<RealmChangesPointer>>()
@@ -1786,6 +1800,7 @@ actual object RealmInterop {
17861800

17871801
actual fun realm_dictionary_add_notification_callback(
17881802
map: RealmMapPointer,
1803+
keyPaths: RealmKeyPathArrayPointer?,
17891804
callback: Callback<RealmChangesPointer>
17901805
): RealmNotificationTokenPointer {
17911806
return CPointerWrapper(
@@ -1798,7 +1813,7 @@ actual object RealmInterop {
17981813
?.dispose()
17991814
?: error("Notification callback data should never be null")
18001815
},
1801-
null, // See https://github.com/realm/realm-kotlin/issues/661
1816+
keyPaths?.cptr(),
18021817
staticCFunction { userdata, change -> // Change callback
18031818
try {
18041819
userdata?.asStableRef<Callback<RealmChangesPointer>>()

packages/external/core

Submodule core updated 109 files

packages/jni-swig-stub/realm.i

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,8 @@ return $jnicall;
301301
realm_flx_sync_mutable_subscription_set_t*, realm_flx_sync_subscription_desc_t*,
302302
realm_set_t*, realm_async_open_task_t*, realm_dictionary_t*,
303303
realm_sync_session_connection_state_notification_token_t*,
304-
realm_dictionary_changes_t*, realm_scheduler_t*, realm_sync_socket_t* };
304+
realm_dictionary_changes_t*, realm_scheduler_t*, realm_sync_socket_t*,
305+
realm_key_path_array_t* };
305306

306307
// For all functions returning a pointer or bool, check for null/false and throw an error if
307308
// realm_get_last_error returns true.
@@ -397,6 +398,66 @@ import static io.realm.kotlin.internal.interop.realm_errno_e.*;
397398
%include "enumtypeunsafe.swg"
398399
%javaconst(1);
399400

401+
// Add support for String[] vs char** conversion
402+
// See https://www.swig.org/Doc4.0/Java.html#Java_converting_java_string_arrays
403+
// Begin --
404+
405+
/* This tells SWIG to treat char ** as a special case when used as a parameter
406+
in a function call */
407+
%typemap(in) char ** (jint size) {
408+
int i = 0;
409+
size = jenv->GetArrayLength($input);
410+
$1 = (char **) malloc((size+1)*sizeof(char *));
411+
/* make a copy of each string */
412+
for (i = 0; i<size; i++) {
413+
jstring j_string = (jstring)jenv->GetObjectArrayElement($input, i);
414+
const char * c_string = jenv->GetStringUTFChars(j_string, 0);
415+
$1[i] = (char*) malloc((strlen(c_string)+1)*sizeof(char));
416+
strcpy($1[i], c_string);
417+
jenv->ReleaseStringUTFChars(j_string, c_string);
418+
jenv->DeleteLocalRef(j_string);
419+
}
420+
$1[i] = 0;
421+
}
422+
423+
/* This cleans up the memory we malloc'd before the function call */
424+
%typemap(freearg) char ** {
425+
int i;
426+
for (i=0; i<size$argnum-1; i++) {
427+
free($1[i]);
428+
}
429+
free($1);
430+
}
431+
432+
/* This allows a C function to return a char ** as a Java String array */
433+
%typemap(out) char ** {
434+
int i;
435+
int len=0;
436+
jstring temp_string;
437+
const jclass clazz = jenv->FindClass("java/lang/String");
438+
439+
while ($1[len]) len++;
440+
jresult = jenv->NewObjectArray(len, clazz, NULL);
441+
/* exception checking omitted */
442+
for (i=0; i<len; i++) {
443+
temp_string = (*jenv)->NewStringUTF(*result++);
444+
jenv->SetObjectArrayElement(jresult, i, temp_string);
445+
jenv->DeleteLocalRef(temp_string);
446+
}
447+
}
448+
449+
/* These 3 typemaps tell SWIG what JNI and Java types to use */
450+
%typemap(jni) char ** "jobjectArray"
451+
%typemap(jtype) char ** "String[]"
452+
%typemap(jstype) char ** "String[]"
453+
454+
/* These 2 typemaps handle the conversion of the jtype to jstype typemap type
455+
and vice versa */
456+
%typemap(javain) char ** "$javainput"
457+
%typemap(javaout) char ** {
458+
return $jnicall;
459+
}
460+
// -- End
400461

401462
// FIXME OPTIMIZE Support getting/setting multiple attributes. Ignored for now due to incorrect
402463
// type cast in Swig-generated wrapper for "const realm_property_key_t*" which is not cast
@@ -430,6 +491,12 @@ import static io.realm.kotlin.internal.interop.realm_errno_e.*;
430491
// realm_convert_with_path.
431492
%ignore realm_convert_with_path;
432493

494+
%ignore "realm_object_add_notification_callback";
495+
%ignore "realm_list_add_notification_callback";
496+
%ignore "realm_set_add_notification_callback";
497+
%ignore "realm_dictionary_add_notification_callback";
498+
%ignore "realm_results_add_notification_callback";
499+
433500
// Swig doesn't understand __attribute__ so eliminate it
434501
#define __attribute__(x)
435502

0 commit comments

Comments
 (0)