Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Keypath filtering in notifications #1547

Merged
merged 27 commits into from
Dec 1, 2023
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* None.

### Enhancements
* 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))
* Support for experimental K2-compilation with `kotlin.experimental.tryK2=true`. (Issue [#1483](https://github.com/realm/realm-kotlin/issues/1483))

### Fixed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ const val UUID_BYTES_SIZE = 16
interface CapiT
interface RealmConfigT : CapiT
interface RealmSchemaT : CapiT
interface RealmObjectSchemaT : CapiT
cmelchior marked this conversation as resolved.
Show resolved Hide resolved
interface RealmT : CapiT
interface LiveRealmT : RealmT
interface FrozenRealmT : RealmT
Expand All @@ -69,6 +70,7 @@ interface RealmCallbackTokenT : CapiT
interface RealmNotificationTokenT : CapiT
interface RealmChangesT : CapiT
interface RealmSchedulerT : CapiT
interface RealmKeyPathArrayT : CapiT

// Public type aliases binding to internal verbose type safe type definitions. This should allow us
// to easily change implementation details later on.
Expand All @@ -88,6 +90,7 @@ typealias RealmCallbackTokenPointer = NativePointer<RealmCallbackTokenT>
typealias RealmNotificationTokenPointer = NativePointer<RealmNotificationTokenT>
typealias RealmChangesPointer = NativePointer<RealmChangesT>
typealias RealmSchedulerPointer = NativePointer<RealmSchedulerT>
typealias RealmKeyPathArrayPointer = NativePointer<RealmKeyPathArrayT>

// Sync types
// Pure marker interfaces corresponding to the C-API realm_x_t struct types
Expand Down Expand Up @@ -435,24 +438,30 @@ expect object RealmInterop {
): RealmObjectPointer?
fun realm_object_delete(obj: RealmObjectPointer)

fun create_key_paths_array(realm: RealmPointer, clazz: ClassKey, keyPaths: List<String>): RealmKeyPathArrayPointer
fun realm_object_add_notification_callback(
obj: RealmObjectPointer,
keyPaths: RealmKeyPathArrayPointer?,
callback: Callback<RealmChangesPointer>
): RealmNotificationTokenPointer
fun realm_results_add_notification_callback(
results: RealmResultsPointer,
keyPaths: RealmKeyPathArrayPointer?,
callback: Callback<RealmChangesPointer>
): RealmNotificationTokenPointer
fun realm_list_add_notification_callback(
list: RealmListPointer,
keyPaths: RealmKeyPathArrayPointer?,
callback: Callback<RealmChangesPointer>
): RealmNotificationTokenPointer
fun realm_set_add_notification_callback(
set: RealmSetPointer,
keyPaths: RealmKeyPathArrayPointer?,
callback: Callback<RealmChangesPointer>
): RealmNotificationTokenPointer
fun realm_dictionary_add_notification_callback(
map: RealmMapPointer,
keyPaths: RealmKeyPathArrayPointer?,
callback: Callback<RealmChangesPointer>
): RealmNotificationTokenPointer
fun realm_object_changes_get_modified_properties(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -813,14 +813,22 @@ actual object RealmInterop {
return realmc.realm_dictionary_is_valid(dictionary.cptr())
}

actual fun create_key_paths_array(realm: RealmPointer, clazz: ClassKey, keyPaths: List<String>): RealmKeyPathArrayPointer {
val ptr = realmc.jni_realm_create_key_path_array(realm.cptr(), clazz.key, keyPaths.size, keyPaths.toTypedArray())
return LongPointerWrapper(ptr)
}

actual fun realm_object_add_notification_callback(
obj: RealmObjectPointer,
keyPaths: RealmKeyPathArrayPointer?,
callback: Callback<RealmChangesPointer>
): RealmNotificationTokenPointer {

return LongPointerWrapper(
realmc.register_notification_cb(
obj.cptr(),
CollectionType.RLM_COLLECTION_TYPE_NONE.nativeValue,
keyPaths?.cptr() ?: 0,
cmelchior marked this conversation as resolved.
Show resolved Hide resolved
object : NotificationCallback {
override fun onChange(pointer: Long) {
callback.onChange(LongPointerWrapper(realmc.realm_clone(pointer), true))
Expand All @@ -833,11 +841,13 @@ actual object RealmInterop {

actual fun realm_results_add_notification_callback(
results: RealmResultsPointer,
keyPaths: RealmKeyPathArrayPointer?,
callback: Callback<RealmChangesPointer>
): RealmNotificationTokenPointer {
return LongPointerWrapper(
realmc.register_results_notification_cb(
results.cptr(),
keyPaths?.cptr() ?: 0,
object : NotificationCallback {
override fun onChange(pointer: Long) {
callback.onChange(LongPointerWrapper(realmc.realm_clone(pointer), true))
Expand All @@ -850,12 +860,14 @@ actual object RealmInterop {

actual fun realm_list_add_notification_callback(
list: RealmListPointer,
keyPaths: RealmKeyPathArrayPointer?,
callback: Callback<RealmChangesPointer>
): RealmNotificationTokenPointer {
return LongPointerWrapper(
realmc.register_notification_cb(
list.cptr(),
CollectionType.RLM_COLLECTION_TYPE_LIST.nativeValue,
keyPaths?.cptr() ?: 0,
object : NotificationCallback {
override fun onChange(pointer: Long) {
callback.onChange(LongPointerWrapper(realmc.realm_clone(pointer), true))
Expand All @@ -868,12 +880,14 @@ actual object RealmInterop {

actual fun realm_set_add_notification_callback(
set: RealmSetPointer,
keyPaths: RealmKeyPathArrayPointer?,
callback: Callback<RealmChangesPointer>
): RealmNotificationTokenPointer {
return LongPointerWrapper(
realmc.register_notification_cb(
set.cptr(),
CollectionType.RLM_COLLECTION_TYPE_SET.nativeValue,
keyPaths?.cptr() ?: 0,
object : NotificationCallback {
override fun onChange(pointer: Long) {
callback.onChange(LongPointerWrapper(realmc.realm_clone(pointer), true))
Expand All @@ -886,12 +900,14 @@ actual object RealmInterop {

actual fun realm_dictionary_add_notification_callback(
map: RealmMapPointer,
keyPaths: RealmKeyPathArrayPointer?,
callback: Callback<RealmChangesPointer>
): RealmNotificationTokenPointer {
return LongPointerWrapper(
realmc.register_notification_cb(
map.cptr(),
CollectionType.RLM_COLLECTION_TYPE_DICTIONARY.nativeValue,
keyPaths?.cptr() ?: 0,
object : NotificationCallback {
override fun onChange(pointer: Long) {
callback.onChange(LongPointerWrapper(realmc.realm_clone(pointer), true))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import kotlinx.cinterop.COpaquePointer
import kotlinx.cinterop.CPointed
import kotlinx.cinterop.CPointer
import kotlinx.cinterop.CPointerVar
import kotlinx.cinterop.CPointerVarOf
import kotlinx.cinterop.CValue
import kotlinx.cinterop.CVariable
import kotlinx.cinterop.LongVar
Expand All @@ -70,6 +71,7 @@ import kotlinx.cinterop.readValue
import kotlinx.cinterop.refTo
import kotlinx.cinterop.set
import kotlinx.cinterop.staticCFunction
import kotlinx.cinterop.toCStringArray
import kotlinx.cinterop.toKString
import kotlinx.cinterop.useContents
import kotlinx.cinterop.usePinned
Expand Down Expand Up @@ -103,6 +105,7 @@ import realm_wrapper.realm_http_header_t
import realm_wrapper.realm_http_request_method
import realm_wrapper.realm_http_request_t
import realm_wrapper.realm_http_response_t
import realm_wrapper.realm_key_path_array_t
import realm_wrapper.realm_link_t
import realm_wrapper.realm_list_t
import realm_wrapper.realm_object_id_t
Expand Down Expand Up @@ -1653,8 +1656,18 @@ actual object RealmInterop {
checkedBooleanResult(realm_wrapper.realm_object_delete(obj.cptr()))
}

actual fun create_key_paths_array(realm: RealmPointer, clazz: ClassKey, keyPaths: List<String>): RealmKeyPathArrayPointer {
memScoped {
val kps: CPointer<CPointerVarOf<CPointer<ByteVarOf<Byte>>>> = keyPaths.toCStringArray(this)
val kp = allocArray<CPointerVar<realm_key_path_array_t>>(1)
checkedBooleanResult(realm_wrapper.realm_create_key_path_array(realm.cptr(), clazz.key.toUInt(), keyPaths.size, kps, kp))
return CPointerWrapper(kp[0])
}
}

actual fun realm_object_add_notification_callback(
obj: RealmObjectPointer,
keyPaths: RealmKeyPathArrayPointer?,
callback: Callback<RealmChangesPointer>
): RealmNotificationTokenPointer {
return CPointerWrapper(
Expand All @@ -1667,7 +1680,7 @@ actual object RealmInterop {
?.dispose()
?: error("Notification callback data should never be null")
},
null, // See https://github.com/realm/realm-kotlin/issues/661
keyPaths?.cptr(),
staticCFunction { userdata, change -> // Change callback
try {
userdata?.asStableRef<Callback<RealmChangesPointer>>()
Expand All @@ -1688,6 +1701,7 @@ actual object RealmInterop {

actual fun realm_results_add_notification_callback(
results: RealmResultsPointer,
keyPaths: RealmKeyPathArrayPointer?,
callback: Callback<RealmChangesPointer>
): RealmNotificationTokenPointer {
return CPointerWrapper(
Expand All @@ -1700,7 +1714,7 @@ actual object RealmInterop {
?.dispose()
?: error("Notification callback data should never be null")
},
null, // See https://github.com/realm/realm-kotlin/issues/661
keyPaths?.cptr(),
staticCFunction { userdata, change -> // Change callback
try {
userdata?.asStableRef<Callback<RealmChangesPointer>>()
Expand All @@ -1721,6 +1735,7 @@ actual object RealmInterop {

actual fun realm_list_add_notification_callback(
list: RealmListPointer,
keyPaths: RealmKeyPathArrayPointer?,
callback: Callback<RealmChangesPointer>
): RealmNotificationTokenPointer {
return CPointerWrapper(
Expand All @@ -1732,7 +1747,7 @@ actual object RealmInterop {
userdata?.asStableRef<Callback<RealmChangesPointer>>()?.dispose()
?: error("Notification callback data should never be null")
},
null, // See https://github.com/realm/realm-kotlin/issues/661
keyPaths?.cptr(),
staticCFunction { userdata, change -> // Change callback
try {
userdata?.asStableRef<Callback<RealmChangesPointer>>()
Expand All @@ -1753,6 +1768,7 @@ actual object RealmInterop {

actual fun realm_set_add_notification_callback(
set: RealmSetPointer,
keyPaths: RealmKeyPathArrayPointer?,
callback: Callback<RealmChangesPointer>
): RealmNotificationTokenPointer {
return CPointerWrapper(
Expand All @@ -1765,7 +1781,7 @@ actual object RealmInterop {
?.dispose()
?: error("Notification callback data should never be null")
},
null, // See https://github.com/realm/realm-kotlin/issues/661
keyPaths?.cptr(),
staticCFunction { userdata, change -> // Change callback
try {
userdata?.asStableRef<Callback<RealmChangesPointer>>()
Expand All @@ -1786,6 +1802,7 @@ actual object RealmInterop {

actual fun realm_dictionary_add_notification_callback(
map: RealmMapPointer,
keyPaths: RealmKeyPathArrayPointer?,
callback: Callback<RealmChangesPointer>
): RealmNotificationTokenPointer {
return CPointerWrapper(
Expand All @@ -1798,7 +1815,7 @@ actual object RealmInterop {
?.dispose()
?: error("Notification callback data should never be null")
},
null, // See https://github.com/realm/realm-kotlin/issues/661
keyPaths?.cptr(),
staticCFunction { userdata, change -> // Change callback
try {
userdata?.asStableRef<Callback<RealmChangesPointer>>()
Expand Down
2 changes: 1 addition & 1 deletion packages/external/core
rorbech marked this conversation as resolved.
Show resolved Hide resolved
68 changes: 67 additions & 1 deletion packages/jni-swig-stub/realm.i
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,8 @@ return $jnicall;
realm_flx_sync_mutable_subscription_set_t*, realm_flx_sync_subscription_desc_t*,
realm_set_t*, realm_async_open_task_t*, realm_dictionary_t*,
realm_sync_session_connection_state_notification_token_t*,
realm_dictionary_changes_t*, realm_scheduler_t*, realm_sync_socket_t* };
realm_dictionary_changes_t*, realm_scheduler_t*, realm_sync_socket_t*,
realm_key_path_array_t* };

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

// Add support for String[] vs char** conversion
// See https://www.swig.org/Doc4.0/Java.html#Java_converting_java_string_arrays
// Begin --

/* This tells SWIG to treat char ** as a special case when used as a parameter
in a function call */
%typemap(in) char ** (jint size) {
int i = 0;
size = jenv->GetArrayLength($input);
$1 = (char **) malloc((size+1)*sizeof(char *));
/* make a copy of each string */
for (i = 0; i<size; i++) {
jstring j_string = (jstring)jenv->GetObjectArrayElement($input, i);
const char * c_string = jenv->GetStringUTFChars(j_string, 0);
$1[i] = (char*) malloc((strlen(c_string)+1)*sizeof(char));
strcpy($1[i], c_string);
jenv->ReleaseStringUTFChars(j_string, c_string);
jenv->DeleteLocalRef(j_string);
}
$1[i] = 0;
}

/* This cleans up the memory we malloc'd before the function call */
%typemap(freearg) char ** {
int i;
for (i=0; i<size$argnum-1; i++)
free($1[i]);
free($1);
}

/* This allows a C function to return a char ** as a Java String array */
%typemap(out) char ** {
int i;
int len=0;
jstring temp_string;
const jclass clazz = jenv->FindClass("java/lang/String");

while ($1[len]) len++;
jresult = jenv->NewObjectArray(len, clazz, NULL);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check for OOM

/* exception checking omitted */
for (i=0; i<len; i++) {
temp_string = (*jenv)->NewStringUTF(*result++);
jenv->SetObjectArrayElement(jresult, i, temp_string);
jenv->DeleteLocalRef(temp_string);
}
}

/* These 3 typemaps tell SWIG what JNI and Java types to use */
%typemap(jni) char ** "jobjectArray"
%typemap(jtype) char ** "String[]"
%typemap(jstype) char ** "String[]"

/* These 2 typemaps handle the conversion of the jtype to jstype typemap type
and vice versa */
%typemap(javain) char ** "$javainput"
%typemap(javaout) char ** {
return $jnicall;
}
// -- End

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

%ignore "realm_object_add_notification_callback";
%ignore "realm_list_add_notification_callback";
%ignore "realm_set_add_notification_callback";
%ignore "realm_dictionary_add_notification_callback";
%ignore "realm_results_add_notification_callback";

// Swig doesn't understand __attribute__ so eliminate it
#define __attribute__(x)

Expand Down
Loading