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 25 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
3 changes: 2 additions & 1 deletion 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 All @@ -25,7 +26,7 @@
* Minimum R8: 8.0.34.

### Internal
* None.
* Updated to Realm Core 13.24.0, commit e593a5f19d0dc205db931ec5618a8c10c95cac90.


## 1.12.1-SNAPSHOT (YYYY-MM-DD)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,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 +89,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 +437,30 @@ expect object RealmInterop {
): RealmObjectPointer?
fun realm_object_delete(obj: RealmObjectPointer)

fun realm_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 @@ -41,6 +41,10 @@ import org.mongodb.kbson.ObjectId
actual val INVALID_CLASS_KEY: ClassKey by lazy { ClassKey(realmc.getRLM_INVALID_CLASS_KEY()) }
actual val INVALID_PROPERTY_KEY: PropertyKey by lazy { PropertyKey(realmc.getRLM_INVALID_PROPERTY_KEY()) }

// The value to pass to JNI functions that accept longs as replacements for pointers and need
// to represent null.
const val NULL_POINTER_VALUE = 0L

/**
* JVM/Android interop implementation.
*
Expand Down Expand Up @@ -813,14 +817,22 @@ actual object RealmInterop {
return realmc.realm_dictionary_is_valid(dictionary.cptr())
}

actual fun realm_create_key_paths_array(realm: RealmPointer, clazz: ClassKey, keyPaths: List<String>): RealmKeyPathArrayPointer {
val ptr = realmc.realm_create_key_path_array(realm.cptr(), clazz.key, keyPaths.size.toLong(), 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() ?: NULL_POINTER_VALUE,
object : NotificationCallback {
override fun onChange(pointer: Long) {
callback.onChange(LongPointerWrapper(realmc.realm_clone(pointer), true))
Expand All @@ -833,11 +845,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() ?: NULL_POINTER_VALUE,
object : NotificationCallback {
override fun onChange(pointer: Long) {
callback.onChange(LongPointerWrapper(realmc.realm_clone(pointer), true))
Expand All @@ -850,12 +864,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() ?: NULL_POINTER_VALUE,
object : NotificationCallback {
override fun onChange(pointer: Long) {
callback.onChange(LongPointerWrapper(realmc.realm_clone(pointer), true))
Expand All @@ -868,12 +884,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() ?: NULL_POINTER_VALUE,
object : NotificationCallback {
override fun onChange(pointer: Long) {
callback.onChange(LongPointerWrapper(realmc.realm_clone(pointer), true))
Expand All @@ -886,12 +904,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() ?: NULL_POINTER_VALUE,
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 @@ -1653,8 +1655,17 @@ actual object RealmInterop {
checkedBooleanResult(realm_wrapper.realm_object_delete(obj.cptr()))
}

actual fun realm_create_key_paths_array(realm: RealmPointer, clazz: ClassKey, keyPaths: List<String>): RealmKeyPathArrayPointer {
memScoped {
val userKeyPaths: CPointer<CPointerVarOf<CPointer<ByteVarOf<Byte>>>> = keyPaths.toCStringArray(this)
val keyPathPointer = realm_wrapper.realm_create_key_path_array(realm.cptr(), clazz.key.toUInt(), keyPaths.size.toULong(), userKeyPaths)
return CPointerWrapper(keyPathPointer)
}
}

actual fun realm_object_add_notification_callback(
obj: RealmObjectPointer,
keyPaths: RealmKeyPathArrayPointer?,
callback: Callback<RealmChangesPointer>
): RealmNotificationTokenPointer {
return CPointerWrapper(
Expand All @@ -1667,7 +1678,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 +1699,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 +1712,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 +1733,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 +1745,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 +1766,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 +1779,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 +1800,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 +1813,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
Submodule core updated 109 files
69 changes: 68 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,66 @@ 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 +491,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