From 97a4f39036f06d30ef7df8b215d0eaef55239ced Mon Sep 17 00:00:00 2001 From: Markus Hintersteiner Date: Tue, 12 Nov 2024 10:37:52 +0100 Subject: [PATCH 1/4] Fix testTag not working in new versions of Jetpack Compose --- .../gestures/ComposeGestureTargetLocator.java | 18 +++++++++++++++++- sentry-compose/proguard-rules.pro | 1 + 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/sentry-compose-helper/src/jvmMain/java/io/sentry/compose/gestures/ComposeGestureTargetLocator.java b/sentry-compose-helper/src/jvmMain/java/io/sentry/compose/gestures/ComposeGestureTargetLocator.java index 8f8ab283e92..ea6a4d80c35 100644 --- a/sentry-compose-helper/src/jvmMain/java/io/sentry/compose/gestures/ComposeGestureTargetLocator.java +++ b/sentry-compose-helper/src/jvmMain/java/io/sentry/compose/gestures/ComposeGestureTargetLocator.java @@ -1,5 +1,6 @@ package io.sentry.compose.gestures; +import androidx.compose.ui.Modifier; import androidx.compose.ui.geometry.Rect; import androidx.compose.ui.layout.ModifierInfo; import androidx.compose.ui.node.LayoutNode; @@ -13,6 +14,7 @@ import io.sentry.compose.helper.BuildConfig; import io.sentry.internal.gestures.GestureTargetLocator; import io.sentry.internal.gestures.UiElement; +import java.lang.reflect.Field; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -90,13 +92,27 @@ public ComposeGestureTargetLocator(final @NotNull ILogger logger) { } } } else { + final @NotNull Modifier modifier = modifierInfo.getModifier(); // Newer Jetpack Compose 1.5 uses Node modifiers for clicks/scrolls - final @Nullable String type = modifierInfo.getModifier().getClass().getCanonicalName(); + final @Nullable String type = modifier.getClass().getCanonicalName(); if ("androidx.compose.foundation.ClickableElement".equals(type) || "androidx.compose.foundation.CombinedClickableElement".equals(type)) { isClickable = true; } else if ("androidx.compose.foundation.ScrollingLayoutElement".equals(type)) { isScrollable = true; + } else if ("androidx.compose.ui.platform.TestTagElement".equals(type)) { + // Newer Jetpack Compose uses TestTagElement as node elements + // See + // https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/TestTag.kt;l=34;drc=dcaa116fbfda77e64a319e1668056ce3b032469f + try { + final Field tagField = modifier.getClass().getField("tag"); + final @Nullable Object value = tagField.get(modifier); + if (value instanceof String) { + lastKnownTag = (String) value; + } + } catch (Throwable e) { + // ignored + } } } } diff --git a/sentry-compose/proguard-rules.pro b/sentry-compose/proguard-rules.pro index 80580b28fd1..372d2db1db0 100644 --- a/sentry-compose/proguard-rules.pro +++ b/sentry-compose/proguard-rules.pro @@ -12,6 +12,7 @@ -keepnames class androidx.compose.foundation.ClickableElement -keepnames class androidx.compose.foundation.CombinedClickableElement -keepnames class androidx.compose.foundation.ScrollingLayoutElement +-keepnames class androidx.compose.ui.platform.TestTagElement { *; } # R8 will warn about missing classes if people don't have androidx.compose-navigation on their # classpath, but this is fine, these classes are used in an internal class which is only used when From 23757c856ad7c1f7d69d2521600bd38d291e0ccc Mon Sep 17 00:00:00 2001 From: Markus Hintersteiner Date: Tue, 12 Nov 2024 11:20:23 +0100 Subject: [PATCH 2/4] Ensure field is accessible --- .../io/sentry/compose/gestures/ComposeGestureTargetLocator.java | 1 + 1 file changed, 1 insertion(+) diff --git a/sentry-compose-helper/src/jvmMain/java/io/sentry/compose/gestures/ComposeGestureTargetLocator.java b/sentry-compose-helper/src/jvmMain/java/io/sentry/compose/gestures/ComposeGestureTargetLocator.java index ea6a4d80c35..2478790d429 100644 --- a/sentry-compose-helper/src/jvmMain/java/io/sentry/compose/gestures/ComposeGestureTargetLocator.java +++ b/sentry-compose-helper/src/jvmMain/java/io/sentry/compose/gestures/ComposeGestureTargetLocator.java @@ -106,6 +106,7 @@ public ComposeGestureTargetLocator(final @NotNull ILogger logger) { // https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/TestTag.kt;l=34;drc=dcaa116fbfda77e64a319e1668056ce3b032469f try { final Field tagField = modifier.getClass().getField("tag"); + tagField.setAccessible(true); final @Nullable Object value = tagField.get(modifier); if (value instanceof String) { lastKnownTag = (String) value; From 153a3ac14443087729600d6f7d21de99885bf0bc Mon Sep 17 00:00:00 2001 From: Markus Hintersteiner Date: Tue, 26 Nov 2024 14:36:23 +0100 Subject: [PATCH 3/4] Update changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e98673f036..837f4c220e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +### Fixes + +- Fix testTag not working for Jetpack Compose user interaction tracking ([#3878](https://github.com/getsentry/sentry-java/pull/3878)) + ## 7.18.0 ### Features From 2d79b45b377b70ed757cb989250bdf3d523c6236 Mon Sep 17 00:00:00 2001 From: Markus Hintersteiner Date: Tue, 26 Nov 2024 15:44:16 +0100 Subject: [PATCH 4/4] Fix use getDeclaredField instead of getField --- .../io/sentry/compose/gestures/ComposeGestureTargetLocator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-compose-helper/src/jvmMain/java/io/sentry/compose/gestures/ComposeGestureTargetLocator.java b/sentry-compose-helper/src/jvmMain/java/io/sentry/compose/gestures/ComposeGestureTargetLocator.java index 2478790d429..aaf085f4841 100644 --- a/sentry-compose-helper/src/jvmMain/java/io/sentry/compose/gestures/ComposeGestureTargetLocator.java +++ b/sentry-compose-helper/src/jvmMain/java/io/sentry/compose/gestures/ComposeGestureTargetLocator.java @@ -105,7 +105,7 @@ public ComposeGestureTargetLocator(final @NotNull ILogger logger) { // See // https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/TestTag.kt;l=34;drc=dcaa116fbfda77e64a319e1668056ce3b032469f try { - final Field tagField = modifier.getClass().getField("tag"); + final Field tagField = modifier.getClass().getDeclaredField("tag"); tagField.setAccessible(true); final @Nullable Object value = tagField.get(modifier); if (value instanceof String) {