From b2ffd34a392de2bddba5ee13248796ccc2db6039 Mon Sep 17 00:00:00 2001 From: HyunWoo Lee Date: Mon, 12 May 2025 16:35:07 -0700 Subject: [PATCH] Refactor ReactAndroid to use `buildReadableMap`, `buildReadableArray` DSL (#51145) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: This PR refactors the entire ReactAndroid package to replace manual `Arguments.createMap()…` and `Arguments.createArray()…` calls with the new Kotlin DSL helpers `buildReadableMap { … }` and `buildReadableArray { … }`. All eligible call sites have been migrated to the DSL, except in functions whose signatures explicitly declare or return WritableMap or WritableArray. No runtime behavior changes are introduced; existing functionality and tests continue to pass unchanged. ## Changelog: [ANDROID] [CHANGED] Apply Collections DSL on ReactAndroid package Pull Request resolved: https://github.com/facebook/react-native/pull/51145 Test Plan: ``` yarn android yarn test-android ``` Reviewed By: rshest Differential Revision: D74401357 Pulled By: cortinico fbshipit-source-id: 0f7b7dfbb7b495675bc4730bdf018666e9041884 --- .../ReactAndroid/api/ReactAndroid.api | 4 + .../react/animated/NativeAnimatedModule.kt | 28 +++--- .../animated/NativeAnimatedNodesManager.kt | 68 +++++++------- .../facebook/react/bridge/JSONArguments.kt | 73 +++++++-------- .../react/bridge/ReadableArrayBuilder.kt | 8 ++ .../react/bridge/ReadableMapBuilder.kt | 28 ++++-- .../modules/appearance/AppearanceModule.kt | 5 +- .../facebook/react/modules/blob/BlobModule.kt | 10 ++- .../modules/core/DeviceEventManagerModule.kt | 5 +- .../react/modules/debug/DevSettingsModule.kt | 5 +- .../react/modules/image/ImageLoaderModule.kt | 40 +++++---- .../react/modules/network/ResponseUtil.kt | 47 +++++----- .../react/modules/share/ShareModule.kt | 7 +- .../modules/websocket/WebSocketModule.kt | 90 +++++++++++-------- .../facebook/react/uimanager/OnLayoutEvent.kt | 14 +-- .../react/views/scroll/ScrollEvent.kt | 45 ++++++---- .../react/views/text/FontMetricsUtil.kt | 27 +++--- .../textinput/ReactContentSizeChangedEvent.kt | 10 +-- .../textinput/ReactTextInputSelectionEvent.kt | 10 +-- 19 files changed, 289 insertions(+), 235 deletions(-) diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index 4eb83cf2dc2..01943cb1d2d 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -1353,6 +1353,8 @@ public final class com/facebook/react/bridge/ReadableArrayBuilder { public final fun add (D)V public final fun add (I)V public final fun add (J)V + public final fun add (Lcom/facebook/react/bridge/ReadableArray;)V + public final fun add (Lcom/facebook/react/bridge/ReadableMap;)V public final fun add (Ljava/lang/String;)V public final fun add (Z)V public final fun addArray (Lkotlin/jvm/functions/Function1;)V @@ -1386,6 +1388,8 @@ public final class com/facebook/react/bridge/ReadableMapBuilder { public final fun put (Ljava/lang/String;D)V public final fun put (Ljava/lang/String;I)V public final fun put (Ljava/lang/String;J)V + public final fun put (Ljava/lang/String;Lcom/facebook/react/bridge/ReadableArray;)V + public final fun put (Ljava/lang/String;Lcom/facebook/react/bridge/ReadableMap;)V public final fun put (Ljava/lang/String;Ljava/lang/String;)V public final fun put (Ljava/lang/String;Z)V public final fun putArray (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.kt index c3e6d6d7775..7bda4e5929b 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.kt @@ -11,7 +11,6 @@ import androidx.annotation.AnyThread import androidx.annotation.UiThread import com.facebook.common.logging.FLog import com.facebook.fbreact.specs.NativeAnimatedModuleSpec -import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.Callback import com.facebook.react.bridge.LifecycleEventListener import com.facebook.react.bridge.ReactApplicationContext @@ -20,6 +19,7 @@ import com.facebook.react.bridge.ReadableArray import com.facebook.react.bridge.ReadableMap import com.facebook.react.bridge.UIManager import com.facebook.react.bridge.UIManagerListener +import com.facebook.react.bridge.buildReadableMap import com.facebook.react.common.annotations.UnstableReactNativeAPI import com.facebook.react.common.annotations.VisibleForTesting import com.facebook.react.common.build.ReactBuildConfig @@ -230,14 +230,8 @@ public class NativeAnimatedModule(reactContext: ReactApplicationContext?) : return } - val tagsArray = Arguments.createArray() - for (tag in tags) { - tagsArray.pushInt(tag) - } - // emit the event to JS to resync the trees - val onAnimationEndedData = Arguments.createMap() - onAnimationEndedData.putArray("tags", tagsArray) + val onAnimationEndedData = buildReadableMap { putArray("tags") { tags.forEach { add(it) } } } val reactApplicationContext = reactApplicationContextIfActiveOrWarn reactApplicationContext?.emitDeviceEvent("onUserDrivenAnimationEnded", onAnimationEndedData) @@ -543,10 +537,11 @@ public class NativeAnimatedModule(reactContext: ReactApplicationContext?) : } val listener = AnimatedNodeValueListener { value, offset -> - val onAnimatedValueData = Arguments.createMap() - onAnimatedValueData.putInt("tag", tag) - onAnimatedValueData.putDouble("value", value) - onAnimatedValueData.putDouble("offset", offset) + val onAnimatedValueData = buildReadableMap { + put("tag", tag) + put("value", value) + put("offset", offset) + } val reactApplicationContext = reactApplicationContextIfActiveOrWarn reactApplicationContext?.emitDeviceEvent("onAnimatedValueUpdate", onAnimatedValueData) @@ -978,10 +973,11 @@ public class NativeAnimatedModule(reactContext: ReactApplicationContext?) : BatchExecutionOpCodes.OP_START_LISTENING_TO_ANIMATED_NODE_VALUE -> { val tag = opsAndArgs.getInt(i++) val listener = AnimatedNodeValueListener { value, offset -> - val onAnimatedValueData = Arguments.createMap() - onAnimatedValueData.putInt("tag", tag) - onAnimatedValueData.putDouble("value", value) - onAnimatedValueData.putDouble("offset", offset) + val onAnimatedValueData = buildReadableMap { + put("tag", tag) + put("value", value) + put("offset", offset) + } val reactApplicationContext = reactApplicationContextIfActiveOrWarn reactApplicationContext?.emitDeviceEvent( diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedNodesManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedNodesManager.kt index 155cd6292a4..4601dfeb007 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedNodesManager.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedNodesManager.kt @@ -20,6 +20,7 @@ import com.facebook.react.bridge.ReactSoftExceptionLogger import com.facebook.react.bridge.ReadableMap import com.facebook.react.bridge.UiThreadUtil import com.facebook.react.bridge.WritableArray +import com.facebook.react.bridge.buildReadableMap import com.facebook.react.uimanager.UIManagerHelper import com.facebook.react.uimanager.common.UIManagerType import com.facebook.react.uimanager.events.Event @@ -269,20 +270,22 @@ public class NativeAnimatedNodesManager( val animatedValueNonnull = checkNotNull(animation.animatedValue) if (animation.endCallback != null) { // Invoke animation end callback with {finished: false} - val endCallbackResponse = Arguments.createMap() - endCallbackResponse.putBoolean("finished", false) - endCallbackResponse.putDouble("value", animatedValueNonnull.nodeValue) - endCallbackResponse.putDouble("offset", animatedValueNonnull.offset) + val endCallbackResponse = buildReadableMap { + put("finished", false) + put("value", animatedValueNonnull.nodeValue) + put("offset", animatedValueNonnull.offset) + } animation.endCallback?.invoke(endCallbackResponse) } else if (reactApplicationContext != null) { // If no callback is passed in, this /may/ be an animation set up by the single-op // instruction from JS, meaning that no jsi::functions are passed into native and // we communicate via RCTDeviceEventEmitter instead of callbacks. - val params = Arguments.createMap() - params.putInt("animationId", animation.id) - params.putBoolean("finished", false) - params.putDouble("value", animatedValueNonnull.nodeValue) - params.putDouble("offset", animatedValueNonnull.offset) + val params = buildReadableMap { + put("animationId", animation.id) + put("finished", false) + put("value", animatedValueNonnull.nodeValue) + put("offset", animatedValueNonnull.offset) + } events = events ?: Arguments.createArray() events.pushMap(params) } @@ -308,20 +311,22 @@ public class NativeAnimatedNodesManager( if (animation.id == animationId) { if (animation.endCallback != null) { // Invoke animation end callback with {finished: false} - val endCallbackResponse = Arguments.createMap() - endCallbackResponse.putBoolean("finished", false) - endCallbackResponse.putDouble("value", checkNotNull(animation.animatedValue).nodeValue) - endCallbackResponse.putDouble("offset", checkNotNull(animation.animatedValue).offset) + val endCallbackResponse = buildReadableMap { + put("finished", false) + put("value", checkNotNull(animation.animatedValue).nodeValue) + put("offset", checkNotNull(animation.animatedValue).offset) + } checkNotNull(animation.endCallback).invoke(endCallbackResponse) } else if (reactApplicationContext != null) { // If no callback is passed in, this /may/ be an animation set up by the single-op // instruction from JS, meaning that no jsi::functions are passed into native and // we communicate via RCTDeviceEventEmitter instead of callbacks. - val params = Arguments.createMap() - params.putInt("animationId", animation.id) - params.putBoolean("finished", false) - params.putDouble("value", checkNotNull(animation.animatedValue).nodeValue) - params.putDouble("offset", checkNotNull(animation.animatedValue).offset) + val params = buildReadableMap { + put("animationId", animation.id) + put("finished", false) + put("value", checkNotNull(animation.animatedValue).nodeValue) + put("offset", checkNotNull(animation.animatedValue).offset) + } events = events ?: Arguments.createArray() events.pushMap(params) } @@ -425,9 +430,10 @@ public class NativeAnimatedNodesManager( if (reactApplicationContext == null) { return } - val params = Arguments.createMap() - params.putInt("tag", tag) - params.putDouble("value", value) + val params = buildReadableMap { + put("tag", tag) + put("value", value) + } reactApplicationContext.emitDeviceEvent("onNativeAnimatedModuleGetValue", params) } @@ -579,20 +585,22 @@ public class NativeAnimatedNodesManager( if (animation.hasFinished) { val animatedValueNonnull = checkNotNull(animation.animatedValue) if (animation.endCallback != null) { - val endCallbackResponse = Arguments.createMap() - endCallbackResponse.putBoolean("finished", true) - endCallbackResponse.putDouble("value", animatedValueNonnull.nodeValue) - endCallbackResponse.putDouble("offset", animatedValueNonnull.offset) + val endCallbackResponse = buildReadableMap { + put("finished", true) + put("value", animatedValueNonnull.nodeValue) + put("offset", animatedValueNonnull.offset) + } animation.endCallback?.invoke(endCallbackResponse) } else if (reactApplicationContext != null) { // If no callback is passed in, this /may/ be an animation set up by the single-op // instruction from JS, meaning that no jsi::functions are passed into native and // we communicate via RCTDeviceEventEmitter instead of callbacks. - val params = Arguments.createMap() - params.putInt("animationId", animation.id) - params.putBoolean("finished", true) - params.putDouble("value", animatedValueNonnull.nodeValue) - params.putDouble("offset", animatedValueNonnull.offset) + val params = buildReadableMap { + put("animationId", animation.id) + put("finished", true) + put("value", animatedValueNonnull.nodeValue) + put("offset", animatedValueNonnull.offset) + } events = events ?: Arguments.createArray() events.pushMap(params) } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/JSONArguments.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/JSONArguments.kt index 7fd8b7a6f6f..04b5887edf9 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/JSONArguments.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/JSONArguments.kt @@ -31,27 +31,28 @@ public object JSONArguments { @JvmStatic @Throws(JSONException::class) public fun fromJSONObject(obj: JSONObject): ReadableMap { - val result: WritableMap = Arguments.createMap() val keys = obj.keys() - while (keys.hasNext()) { - val key = keys.next() - val value = obj.get(key) + val result = buildReadableMap { + while (keys.hasNext()) { + val key = keys.next() + val value = obj.get(key) - when (value) { - is JSONObject -> result.putMap(key, fromJSONObject(value)) - is JSONArray -> result.putArray(key, fromJSONArray(value)) - is String -> result.putString(key, value) - is Boolean -> result.putBoolean(key, value) - is Int -> result.putInt(key, value) - is Double -> result.putDouble(key, value) - is Long -> result.putInt(key, value.toInt()) - else -> - if (obj.isNull(key)) { - result.putNull(key) - } else { - throw JSONException("Unexpected value when parsing JSON object. key: $key") - } + when (value) { + is JSONObject -> put(key, fromJSONObject(value)) + is JSONArray -> put(key, fromJSONArray(value)) + is String -> put(key, value) + is Boolean -> put(key, value) + is Int -> put(key, value) + is Double -> put(key, value) + is Long -> put(key, value.toInt()) + else -> + if (obj.isNull(key)) { + putNull(key) + } else { + throw JSONException("Unexpected value when parsing JSON object. key: $key") + } + } } } @@ -79,25 +80,25 @@ public object JSONArguments { @JvmStatic @Throws(JSONException::class) public fun fromJSONArray(arr: JSONArray): ReadableArray { - val result: WritableArray = Arguments.createArray() + val result = buildReadableArray { + repeat(arr.length()) { + val value = arr.get(it) - for (i in 0 until arr.length()) { - val value = arr.get(i) - - when (value) { - is JSONObject -> result.pushMap(fromJSONObject(value)) - is JSONArray -> result.pushArray(fromJSONArray(value)) - is String -> result.pushString(value) - is Boolean -> result.pushBoolean(value) - is Int -> result.pushInt(value) - is Double -> result.pushDouble(value) - is Long -> result.pushInt(value.toInt()) - else -> - if (arr.isNull(i)) { - result.pushNull() - } else { - throw JSONException("Unexpected value when parsing JSON array. index: $i") - } + when (value) { + is JSONObject -> add(fromJSONObject(value)) + is JSONArray -> add(fromJSONArray(value)) + is String -> add(value) + is Boolean -> add(value) + is Int -> add(value) + is Double -> add(value) + is Long -> add(value.toInt()) + else -> + if (arr.isNull(it)) { + addNull() + } else { + throw JSONException("Unexpected value when parsing JSON array. index: $it") + } + } } } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableArrayBuilder.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableArrayBuilder.kt index cf125b747c2..f8b62ff2e95 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableArrayBuilder.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableArrayBuilder.kt @@ -47,6 +47,14 @@ public class ReadableArrayBuilder(private val array: WritableArray) { array.pushDouble(value.toDouble()) } + public fun add(value: ReadableMap) { + array.pushMap(value) + } + + public fun add(value: ReadableArray) { + array.pushArray(value) + } + public fun addNull() { array.pushNull() } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableMapBuilder.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableMapBuilder.kt index ff2bd7130d9..23e5539cc71 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableMapBuilder.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableMapBuilder.kt @@ -11,13 +11,19 @@ package com.facebook.react.bridge * Convenience class for building a [ReadableMap] in a Kotlin idiomatic way. You can use it as * follows: * ``` - * val array: ReadableArray = buildReadableArray { - * add("one") - * add(2) - * add(true) - * addNull() - * addMap { put("nestedKey", "nestedValue") } - * } + * val map: ReadableMap = buildReadableMap { + * put("first", "one") + * put("second", 2) + * put("third", true) + * putNull("fourth") + * putMap("fifth") { + * put("nestedKey", "nestedValue") + * } + * putArray("sixth") { + * add(1) + * add("2") + * } + * } * ``` */ public inline fun buildReadableMap(builder: ReadableMapBuilder.() -> Unit): ReadableMap { @@ -51,6 +57,14 @@ public class ReadableMapBuilder(private val map: WritableMap) { map.putNull(key) } + public fun put(key: String, value: ReadableMap) { + map.putMap(key, value) + } + + public fun put(key: String, value: ReadableArray) { + map.putArray(key, value) + } + public fun putMap(key: String, builder: ReadableMapBuilder.() -> Unit) { map.putMap(key, buildReadableMap(builder)) } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/appearance/AppearanceModule.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/appearance/AppearanceModule.kt index 94b73cf482f..fbe604db809 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/appearance/AppearanceModule.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/appearance/AppearanceModule.kt @@ -11,9 +11,9 @@ import android.content.Context import android.content.res.Configuration import androidx.appcompat.app.AppCompatDelegate import com.facebook.fbreact.specs.NativeAppearanceSpec -import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.UiThreadUtil +import com.facebook.react.bridge.buildReadableMap import com.facebook.react.module.annotations.ReactModule /** Module that exposes the user's preferred color scheme. */ @@ -89,8 +89,7 @@ constructor( /** Sends an event to the JS instance that the preferred color scheme has changed. */ public fun emitAppearanceChanged(colorScheme: String) { - val appearancePreferences = Arguments.createMap() - appearancePreferences.putString("colorScheme", colorScheme) + val appearancePreferences = buildReadableMap { put("colorScheme", colorScheme) } val reactApplicationContext = getReactApplicationContextIfActiveOrWarn() reactApplicationContext?.emitDeviceEvent(APPEARANCE_CHANGED_EVENT_NAME, appearancePreferences) } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/blob/BlobModule.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/blob/BlobModule.kt index d29cf5155ff..5dba5dd4c27 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/blob/BlobModule.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/blob/BlobModule.kt @@ -19,6 +19,7 @@ import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReadableArray import com.facebook.react.bridge.ReadableMap import com.facebook.react.bridge.WritableMap +import com.facebook.react.bridge.buildReadableMap import com.facebook.react.module.annotations.ReactModule import com.facebook.react.modules.network.NetworkingModule import com.facebook.react.modules.websocket.WebSocketModule @@ -52,10 +53,11 @@ public class BlobModule(reactContext: ReactApplicationContext) : override fun onMessage(byteString: ByteString, params: WritableMap) { val data = byteString.toByteArray() - val blob = Arguments.createMap() - blob.putString("blobId", store(data)) - blob.putInt("offset", 0) - blob.putInt("size", data.size) + val blob = buildReadableMap { + put("blobId", store(data)) + put("offset", 0) + put("size", data.size) + } params.putMap("data", blob) params.putString("type", "blob") diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/core/DeviceEventManagerModule.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/core/DeviceEventManagerModule.kt index 06f60198a56..a7c1475e89a 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/core/DeviceEventManagerModule.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/core/DeviceEventManagerModule.kt @@ -10,10 +10,10 @@ package com.facebook.react.modules.core import android.net.Uri import com.facebook.fbreact.specs.NativeDeviceEventManagerSpec import com.facebook.proguard.annotations.DoNotStripAny -import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.JavaScriptModule import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.UiThreadUtil +import com.facebook.react.bridge.buildReadableMap import com.facebook.react.module.annotations.ReactModule /** Native module that handles device hardware events like hardware back presses. */ @@ -43,8 +43,7 @@ public open class DeviceEventManagerModule( public open fun emitNewIntentReceived(uri: Uri) { val reactApplicationContext: ReactApplicationContext? = getReactApplicationContextIfActiveOrWarn() - val map = Arguments.createMap() - map.putString("url", uri.toString()) + val map = buildReadableMap { put("url", uri.toString()) } reactApplicationContext?.emitDeviceEvent("url", map) } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/debug/DevSettingsModule.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/debug/DevSettingsModule.kt index 426bcbb5114..26e8e904f4a 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/debug/DevSettingsModule.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/debug/DevSettingsModule.kt @@ -8,9 +8,9 @@ package com.facebook.react.modules.debug import com.facebook.fbreact.specs.NativeDevSettingsSpec -import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.UiThreadUtil +import com.facebook.react.bridge.buildReadableMap import com.facebook.react.devsupport.interfaces.DevSupportManager import com.facebook.react.module.annotations.ReactModule @@ -50,8 +50,7 @@ public class DevSettingsModule( override fun addMenuItem(title: String) { devSupportManager.addCustomDevOption(title) { - val data = Arguments.createMap() - data.putString("title", title) + val data = buildReadableMap { put("title", title) } val reactApplicationContext = reactApplicationContextIfActiveOrWarn reactApplicationContext?.emitDeviceEvent("didPressMenuItem", data) } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/image/ImageLoaderModule.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/image/ImageLoaderModule.kt index b441fb39156..f2a5979ce5a 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/image/ImageLoaderModule.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/image/ImageLoaderModule.kt @@ -20,7 +20,6 @@ import com.facebook.imagepipeline.core.ImagePipeline import com.facebook.imagepipeline.image.CloseableImage import com.facebook.imagepipeline.request.ImageRequest import com.facebook.imagepipeline.request.ImageRequestBuilder -import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.GuardedAsyncTask import com.facebook.react.bridge.LifecycleEventListener import com.facebook.react.bridge.Promise @@ -28,7 +27,7 @@ import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReactMethod import com.facebook.react.bridge.ReadableArray import com.facebook.react.bridge.ReadableMap -import com.facebook.react.bridge.WritableMap +import com.facebook.react.bridge.buildReadableMap import com.facebook.react.module.annotations.ReactModule import com.facebook.react.modules.fresco.ReactNetworkImageRequest import com.facebook.react.views.image.ReactCallerContextFactory @@ -96,9 +95,10 @@ internal class ImageLoaderModule : NativeImageLoaderAndroidSpec, LifecycleEventL if (ref != null) { try { val image: CloseableImage = ref.get() - val sizes: WritableMap = Arguments.createMap() - sizes.putInt("width", image.width) - sizes.putInt("height", image.height) + val sizes = buildReadableMap { + put("width", image.width) + put("height", image.height) + } promise.resolve(sizes) } catch (e: Exception) { promise.reject(ERROR_GET_SIZE_FAILURE, e) @@ -131,7 +131,7 @@ internal class ImageLoaderModule : NativeImageLoaderAndroidSpec, LifecycleEventL promise.reject(ERROR_INVALID_URI, "Cannot get the size of an image for an empty URI") return } - val source = ImageSource(getReactApplicationContext(), uriString) + val source = ImageSource(reactApplicationContext, uriString) val imageRequestBuilder: ImageRequestBuilder = ImageRequestBuilder.newBuilderWithSource(source.uri) val request: ImageRequest = @@ -148,9 +148,10 @@ internal class ImageLoaderModule : NativeImageLoaderAndroidSpec, LifecycleEventL if (ref != null) { try { val image: CloseableImage = ref.get() - val sizes: WritableMap = Arguments.createMap() - sizes.putInt("width", image.width) - sizes.putInt("height", image.height) + val sizes = buildReadableMap { + put("width", image.width) + put("height", image.height) + } promise.resolve(sizes) } catch (e: Exception) { promise.reject(ERROR_GET_SIZE_FAILURE, e) @@ -227,16 +228,17 @@ internal class ImageLoaderModule : NativeImageLoaderAndroidSpec, LifecycleEventL @Suppress("DEPRECATION", "StaticFieldLeak") object : GuardedAsyncTask(getReactApplicationContext()) { override fun doInBackgroundGuarded(vararg params: Void) { - val result: WritableMap = Arguments.createMap() - val imagePipeline: ImagePipeline = this@ImageLoaderModule.imagePipeline - for (i in 0 until uris.size()) { - val uriString = uris.getString(i) - if (!uriString.isNullOrEmpty()) { - val uri = Uri.parse(uriString) - if (imagePipeline.isInBitmapMemoryCache(uri)) { - result.putString(uriString, "memory") - } else if (imagePipeline.isInDiskCacheSync(uri)) { - result.putString(uriString, "disk") + val result = buildReadableMap { + val imagePipeline: ImagePipeline = this@ImageLoaderModule.imagePipeline + repeat(uris.size()) { + val uriString = uris.getString(it) + if (!uriString.isNullOrEmpty()) { + val uri = Uri.parse(uriString) + if (imagePipeline.isInBitmapMemoryCache(uri)) { + put(uriString, "memory") + } else if (imagePipeline.isInDiskCacheSync(uri)) { + put(uriString, "disk") + } } } } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/network/ResponseUtil.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/network/ResponseUtil.kt index cdc970816f3..2d07c193111 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/network/ResponseUtil.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/network/ResponseUtil.kt @@ -10,6 +10,7 @@ package com.facebook.react.modules.network import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.WritableMap +import com.facebook.react.bridge.buildReadableArray import java.net.SocketTimeoutException /** Util methods to send network responses to JS. */ @@ -23,10 +24,10 @@ internal object ResponseUtil { ) { reactContext?.emitDeviceEvent( "didSendNetworkData", - Arguments.createArray().apply { - pushInt(requestId) - pushInt(progress.toInt()) - pushInt(total.toInt()) + buildReadableArray { + add(requestId) + add(progress.toInt()) + add(total.toInt()) }) } @@ -40,11 +41,11 @@ internal object ResponseUtil { ) { reactContext?.emitDeviceEvent( "didReceiveNetworkIncrementalData", - Arguments.createArray().apply { - pushInt(requestId) - pushString(data) - pushInt(progress.toInt()) - pushInt(total.toInt()) + buildReadableArray { + add(requestId) + add(data) + add(progress.toInt()) + add(total.toInt()) }) } @@ -57,10 +58,10 @@ internal object ResponseUtil { ) { reactContext?.emitDeviceEvent( "didReceiveNetworkDataProgress", - Arguments.createArray().apply { - pushInt(requestId) - pushInt(progress.toInt()) - pushInt(total.toInt()) + buildReadableArray { + add(requestId) + add(progress.toInt()) + add(total.toInt()) }) } @@ -68,9 +69,9 @@ internal object ResponseUtil { fun onDataReceived(reactContext: ReactApplicationContext?, requestId: Int, data: String?) { reactContext?.emitDeviceEvent( "didReceiveNetworkData", - Arguments.createArray().apply { - pushInt(requestId) - pushString(data) + buildReadableArray { + add(requestId) + add(data) }) } @@ -93,11 +94,11 @@ internal object ResponseUtil { ) { reactContext?.emitDeviceEvent( "didCompleteNetworkResponse", - Arguments.createArray().apply { - pushInt(requestId) - pushString(error) + buildReadableArray { + add(requestId) + add(error) if (e?.javaClass == SocketTimeoutException::class.java) { - pushBoolean(true) // last argument is a time out boolean + add(true) // last argument is a time out boolean } }) } @@ -106,9 +107,9 @@ internal object ResponseUtil { fun onRequestSuccess(reactContext: ReactApplicationContext?, requestId: Int) { reactContext?.emitDeviceEvent( "didCompleteNetworkResponse", - Arguments.createArray().apply { - pushInt(requestId) - pushNull() + buildReadableArray { + add(requestId) + addNull() }) } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/share/ShareModule.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/share/ShareModule.kt index 9fab84fbc26..209b42411f8 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/share/ShareModule.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/share/ShareModule.kt @@ -9,10 +9,10 @@ package com.facebook.react.modules.share import android.content.Intent import com.facebook.fbreact.specs.NativeShareModuleSpec -import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.Promise import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReadableMap +import com.facebook.react.bridge.buildReadableMap import com.facebook.react.module.annotations.ReactModule /** Intent module. Launch other activities or open URLs. */ @@ -48,10 +48,9 @@ internal class ShareModule(reactContext: ReactApplicationContext) : if (currentActivity != null) { currentActivity.startActivity(chooser) } else { - getReactApplicationContext().startActivity(chooser) + reactApplicationContext.startActivity(chooser) } - val result = Arguments.createMap() - result.putString("action", ACTION_SHARED) + val result = buildReadableMap { put("action", ACTION_SHARED) } promise.resolve(result) } catch (e: Exception) { promise.reject(ERROR_UNABLE_TO_OPEN_DIALOG, "Failed to open share dialog") diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/websocket/WebSocketModule.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/websocket/WebSocketModule.kt index 06c891c51f3..70543da8c74 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/websocket/WebSocketModule.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/websocket/WebSocketModule.kt @@ -17,6 +17,7 @@ import com.facebook.react.bridge.ReadableArray import com.facebook.react.bridge.ReadableMap import com.facebook.react.bridge.ReadableType import com.facebook.react.bridge.WritableMap +import com.facebook.react.bridge.buildReadableMap import com.facebook.react.common.ReactConstants import com.facebook.react.module.annotations.ReactModule import com.facebook.react.modules.network.CustomClientBuilder @@ -56,7 +57,7 @@ public class WebSocketModule(context: ReactApplicationContext) : contentHandlers.clear() } - private fun sendEvent(eventName: String, params: WritableMap) { + private fun sendEvent(eventName: String, params: ReadableMap) { val reactAppContext = reactApplicationContext if (reactAppContext.hasActiveReactInstance()) { reactAppContext.emitDeviceEvent(eventName, params) @@ -141,9 +142,10 @@ public class WebSocketModule(context: ReactApplicationContext) : object : WebSocketListener() { override fun onOpen(webSocket: WebSocket, response: Response) { webSocketConnections[id] = webSocket - val params = Arguments.createMap() - params.putInt("id", id) - params.putString("protocol", response.header("Sec-WebSocket-Protocol", "")) + val params = buildReadableMap { + put("id", id) + put("protocol", response.header("Sec-WebSocket-Protocol", "")) + } sendEvent("websocketOpen", params) } @@ -152,10 +154,11 @@ public class WebSocketModule(context: ReactApplicationContext) : } override fun onClosed(webSocket: WebSocket, code: Int, reason: String) { - val params = Arguments.createMap() - params.putInt("id", id) - params.putInt("code", code) - params.putString("reason", reason) + val params = buildReadableMap { + put("id", id) + put("code", code) + put("reason", reason) + } sendEvent("websocketClosed", params) } @@ -221,14 +224,16 @@ public class WebSocketModule(context: ReactApplicationContext) : val client = webSocketConnections[id] if (client == null) { // This is a programmer error -- display development warning - var params: WritableMap = Arguments.createMap() - params.putInt("id", id) - params.putString("message", "client is null") + var params = buildReadableMap { + put("id", id) + put("message", "client is null") + } sendEvent("websocketFailed", params) - params = Arguments.createMap() - params.putInt("id", id) - params.putInt("code", 0) - params.putString("reason", "client is null") + params = buildReadableMap { + put("id", id) + put("code", 0) + put("reason", "client is null") + } sendEvent("websocketClosed", params) webSocketConnections.remove(id) contentHandlers.remove(id) @@ -246,14 +251,16 @@ public class WebSocketModule(context: ReactApplicationContext) : val client = webSocketConnections[id] if (client == null) { // This is a programmer error -- display development warning - var params: WritableMap = Arguments.createMap() - params.putInt("id", id) - params.putString("message", "client is null") + var params = buildReadableMap { + put("id", id) + put("message", "client is null") + } sendEvent("websocketFailed", params) - params = Arguments.createMap() - params.putInt("id", id) - params.putInt("code", 0) - params.putString("reason", "client is null") + params = buildReadableMap { + put("id", id) + put("code", 0) + put("reason", "client is null") + } sendEvent("websocketClosed", params) webSocketConnections.remove(id) contentHandlers.remove(id) @@ -271,14 +278,16 @@ public class WebSocketModule(context: ReactApplicationContext) : val client = webSocketConnections[id] if (client == null) { // This is a programmer error -- display development warning - var params: WritableMap = Arguments.createMap() - params.putInt("id", id) - params.putString("message", "client is null") + var params = buildReadableMap { + put("id", id) + put("message", "client is null") + } sendEvent("websocketFailed", params) - params = Arguments.createMap() - params.putInt("id", id) - params.putInt("code", 0) - params.putString("reason", "client is null") + params = buildReadableMap { + put("id", id) + put("code", 0) + put("reason", "client is null") + } sendEvent("websocketClosed", params) webSocketConnections.remove(id) contentHandlers.remove(id) @@ -296,14 +305,16 @@ public class WebSocketModule(context: ReactApplicationContext) : val client = webSocketConnections[id] if (client == null) { // This is a programmer error -- display development warning - var params: WritableMap = Arguments.createMap() - params.putInt("id", id) - params.putString("message", "client is null") + var params = buildReadableMap { + put("id", id) + put("message", "client is null") + } sendEvent("websocketFailed", params) - params = Arguments.createMap() - params.putInt("id", id) - params.putInt("code", 0) - params.putString("reason", "client is null") + params = buildReadableMap { + put("id", id) + put("code", 0) + put("reason", "client is null") + } sendEvent("websocketClosed", params) webSocketConnections.remove(id) contentHandlers.remove(id) @@ -317,9 +328,10 @@ public class WebSocketModule(context: ReactApplicationContext) : } private fun notifyWebSocketFailed(id: Int, message: String?) { - val params = Arguments.createMap() - params.putInt("id", id) - params.putString("message", message) + val params = buildReadableMap { + put("id", id) + put("message", message) + } sendEvent("websocketFailed", params) } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/OnLayoutEvent.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/OnLayoutEvent.kt index 932e551c422..f1d4731c894 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/OnLayoutEvent.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/OnLayoutEvent.kt @@ -10,6 +10,7 @@ package com.facebook.react.uimanager import androidx.core.util.Pools.SynchronizedPool import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.WritableMap +import com.facebook.react.bridge.buildReadableMap import com.facebook.react.common.annotations.VisibleForTesting import com.facebook.react.common.annotations.internal.LegacyArchitecture import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel @@ -40,13 +41,12 @@ public class OnLayoutEvent private constructor() : Event() { override fun getEventName(): String = "topLayout" override fun getEventData(): WritableMap { - val layout = - Arguments.createMap().apply { - putDouble("x", toDIPFromPixel(x.toFloat()).toDouble()) - putDouble("y", toDIPFromPixel(y.toFloat()).toDouble()) - putDouble("width", toDIPFromPixel(width.toFloat()).toDouble()) - putDouble("height", toDIPFromPixel(height.toFloat()).toDouble()) - } + val layout = buildReadableMap { + put("x", toDIPFromPixel(x.toFloat()).toDouble()) + put("y", toDIPFromPixel(y.toFloat()).toDouble()) + put("width", toDIPFromPixel(width.toFloat()).toDouble()) + put("height", toDIPFromPixel(height.toFloat()).toDouble()) + } val event = Arguments.createMap().apply { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ScrollEvent.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ScrollEvent.kt index 3a7b7bce206..d3099575d10 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ScrollEvent.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ScrollEvent.kt @@ -13,6 +13,7 @@ import com.facebook.infer.annotation.Assertions import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.ReactSoftExceptionLogger import com.facebook.react.bridge.WritableMap +import com.facebook.react.bridge.buildReadableMap import com.facebook.react.uimanager.PixelUtil.toDIPFromPixel import com.facebook.react.uimanager.common.ViewUtil import com.facebook.react.uimanager.events.Event @@ -74,23 +75,33 @@ public class ScrollEvent private constructor() : Event() { override fun canCoalesce(): Boolean = scrollEventType == ScrollEventType.SCROLL override fun getEventData(): WritableMap { - val contentInset = Arguments.createMap() - contentInset.putDouble("top", 0.0) - contentInset.putDouble("bottom", 0.0) - contentInset.putDouble("left", 0.0) - contentInset.putDouble("right", 0.0) - val contentOffset = Arguments.createMap() - contentOffset.putDouble("x", toDIPFromPixel(scrollX).toDouble()) - contentOffset.putDouble("y", toDIPFromPixel(scrollY).toDouble()) - val contentSize = Arguments.createMap() - contentSize.putDouble("width", toDIPFromPixel(contentWidth.toFloat()).toDouble()) - contentSize.putDouble("height", toDIPFromPixel(contentHeight.toFloat()).toDouble()) - val layoutMeasurement = Arguments.createMap() - layoutMeasurement.putDouble("width", toDIPFromPixel(scrollViewWidth.toFloat()).toDouble()) - layoutMeasurement.putDouble("height", toDIPFromPixel(scrollViewHeight.toFloat()).toDouble()) - val velocity = Arguments.createMap() - velocity.putDouble("x", xVelocity.toDouble()) - velocity.putDouble("y", yVelocity.toDouble()) + val contentInset = buildReadableMap { + put("top", 0.0) + put("bottom", 0.0) + put("left", 0.0) + put("right", 0.0) + } + + val contentOffset = buildReadableMap { + put("x", toDIPFromPixel(scrollX).toDouble()) + put("y", toDIPFromPixel(scrollY).toDouble()) + } + + val contentSize = buildReadableMap { + put("width", toDIPFromPixel(contentWidth.toFloat()).toDouble()) + put("height", toDIPFromPixel(contentHeight.toFloat()).toDouble()) + } + + val layoutMeasurement = buildReadableMap { + put("width", toDIPFromPixel(scrollViewWidth.toFloat()).toDouble()) + put("height", toDIPFromPixel(scrollViewHeight.toFloat()).toDouble()) + } + + val velocity = buildReadableMap { + put("x", toDIPFromPixel(xVelocity).toDouble()) + put("y", toDIPFromPixel(yVelocity).toDouble()) + } + val event = Arguments.createMap() event.putMap("contentInset", contentInset) event.putMap("contentOffset", contentOffset) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/FontMetricsUtil.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/FontMetricsUtil.kt index 9ce5e241766..548b1682103 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/FontMetricsUtil.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/FontMetricsUtil.kt @@ -13,6 +13,7 @@ import android.text.Layout import android.text.TextPaint import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.WritableArray +import com.facebook.react.bridge.buildReadableMap internal object FontMetricsUtil { @@ -51,20 +52,18 @@ internal object FontMetricsUtil { val lineWidth = if (endsWithNewLine) layout.getLineMax(i) else layout.getLineWidth(i) val bounds = Rect() layout.getLineBounds(i, bounds) - val line = - Arguments.createMap().apply { - putDouble("x", (layout.getLineLeft(i) / dm.density).toDouble()) - putDouble("y", (bounds.top / dm.density).toDouble()) - putDouble("width", (lineWidth / dm.density).toDouble()) - putDouble("height", (bounds.height() / dm.density).toDouble()) - putDouble("descender", (layout.getLineDescent(i) / dm.density).toDouble()) - putDouble("ascender", (-layout.getLineAscent(i) / dm.density).toDouble()) - putDouble("baseline", (layout.getLineBaseline(i) / dm.density).toDouble()) - putDouble("capHeight", capHeight.toDouble()) - putDouble("xHeight", xHeight.toDouble()) - putString( - "text", text.subSequence(layout.getLineStart(i), layout.getLineEnd(i)).toString()) - } + val line = buildReadableMap { + put("x", (layout.getLineLeft(i) / dm.density).toDouble()) + put("y", (bounds.top / dm.density).toDouble()) + put("width", (lineWidth / dm.density).toDouble()) + put("height", (bounds.height() / dm.density).toDouble()) + put("descender", (layout.getLineDescent(i) / dm.density).toDouble()) + put("ascender", (-layout.getLineAscent(i) / dm.density).toDouble()) + put("baseline", (layout.getLineBaseline(i) / dm.density).toDouble()) + put("capHeight", capHeight.toDouble()) + put("xHeight", xHeight.toDouble()) + put("text", text.subSequence(layout.getLineStart(i), layout.getLineEnd(i)).toString()) + } lines.pushMap(line) } return lines diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactContentSizeChangedEvent.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactContentSizeChangedEvent.kt index 8eebc476b25..172de4a8d2a 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactContentSizeChangedEvent.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactContentSizeChangedEvent.kt @@ -9,6 +9,7 @@ package com.facebook.react.views.textinput import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.WritableMap +import com.facebook.react.bridge.buildReadableMap import com.facebook.react.uimanager.common.ViewUtil import com.facebook.react.uimanager.events.Event @@ -32,11 +33,10 @@ internal class ReactContentSizeChangedEvent( override fun getEventName(): String = EVENT_NAME override fun getEventData(): WritableMap { - val contentSize = - Arguments.createMap().apply { - putDouble("width", contentWidth.toDouble()) - putDouble("height", contentHeight.toDouble()) - } + val contentSize = buildReadableMap { + put("width", contentWidth.toDouble()) + put("height", contentHeight.toDouble()) + } return Arguments.createMap().apply { putMap("contentSize", contentSize) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputSelectionEvent.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputSelectionEvent.kt index 3f61d2997dd..af873d8c017 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputSelectionEvent.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputSelectionEvent.kt @@ -9,6 +9,7 @@ package com.facebook.react.views.textinput import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.WritableMap +import com.facebook.react.bridge.buildReadableMap import com.facebook.react.uimanager.common.ViewUtil import com.facebook.react.uimanager.events.Event @@ -31,11 +32,10 @@ internal class ReactTextInputSelectionEvent( override fun getEventName(): String = EVENT_NAME override fun getEventData(): WritableMap { - val selectionData = - Arguments.createMap().apply { - putInt("end", selectionEnd) - putInt("start", selectionStart) - } + val selectionData = buildReadableMap { + put("start", selectionStart) + put("end", selectionEnd) + } return Arguments.createMap().apply { putMap("selection", selectionData) } }