11package io .sentry .compose ;
22
3+ import androidx .annotation .NonNull ;
34import androidx .compose .ui .Modifier ;
45import androidx .compose .ui .geometry .Rect ;
56import androidx .compose .ui .layout .LayoutCoordinatesKt ;
1516import org .jetbrains .annotations .NotNull ;
1617import org .jetbrains .annotations .Nullable ;
1718
19+ @ SuppressWarnings ("KotlinInternalInJava" )
1820public class SentryComposeHelper {
1921
2022 private final @ NotNull ILogger logger ;
21- private Field layoutDelegateField = null ;
23+ private final @ Nullable Field layoutDelegateField ;
24+ private final @ Nullable Field testTagElementField ;
25+ private final @ Nullable Field sentryTagElementField ;
2226
2327 @ Nullable
24- public static String extractTag (final @ NotNull Modifier modifier ) {
28+ public String extractTag (final @ NotNull Modifier modifier ) {
2529 final @ Nullable String type = modifier .getClass ().getCanonicalName ();
2630 // Newer Jetpack Compose uses TestTagElement as node elements
2731 // See
2832 // 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
29- if ("androidx.compose.ui.platform.TestTagElement" .equals (type )
30- || "io.sentry.compose.SentryModifier.SentryTagModifierNodeElement" .equals (type )) {
31- try {
32- final Field tagField = modifier .getClass ().getDeclaredField ("tag" );
33- tagField .setAccessible (true );
34- final @ Nullable Object value = tagField .get (modifier );
33+ try {
34+ if ("androidx.compose.ui.platform.TestTagElement" .equals (type )
35+ && testTagElementField != null ) {
36+ final @ Nullable Object value = testTagElementField .get (modifier );
37+ return (String ) value ;
38+ } else if ("io.sentry.compose.SentryModifier.SentryTagModifierNodeElement" .equals (type )
39+ && sentryTagElementField != null ) {
40+ final @ Nullable Object value = sentryTagElementField .get (modifier );
3541 return (String ) value ;
36- } catch (Throwable e ) {
37- // ignored
3842 }
43+ } catch (Throwable e ) {
44+ // ignored
3945 }
4046
4147 // Older versions use SemanticsModifier
@@ -56,13 +62,11 @@ public static String extractTag(final @NotNull Modifier modifier) {
5662
5763 public SentryComposeHelper (final @ NotNull ILogger logger ) {
5864 this .logger = logger ;
59- try {
60- final Class <?> clazz = Class .forName ("androidx.compose.ui.node.LayoutNode" );
61- layoutDelegateField = clazz .getDeclaredField ("layoutDelegate" );
62- layoutDelegateField .setAccessible (true );
63- } catch (Exception e ) {
64- logger .log (SentryLevel .WARNING , "Could not find LayoutNode.layoutDelegate field" );
65- }
65+ layoutDelegateField =
66+ loadField (logger , "androidx.compose.ui.node.LayoutNode" , "layoutDelegate" );
67+ testTagElementField = loadField (logger , "androidx.compose.ui.platform.TestTagElement" , "tag" );
68+ sentryTagElementField =
69+ loadField (logger , "io.sentry.compose.SentryModifier.SentryTagModifierNodeElement" , "tag" );
6670 }
6771
6872 public @ Nullable Rect getLayoutNodeBoundsInWindow (@ NotNull final LayoutNode node ) {
@@ -77,4 +81,18 @@ public SentryComposeHelper(final @NotNull ILogger logger) {
7781 }
7882 return null ;
7983 }
84+
85+ @ Nullable
86+ private static Field loadField (
87+ @ NonNull ILogger logger , final @ NotNull String className , final @ NotNull String fieldName ) {
88+ try {
89+ final Class <?> clazz = Class .forName (className );
90+ final @ Nullable Field field = clazz .getDeclaredField (fieldName );
91+ field .setAccessible (true );
92+ return field ;
93+ } catch (Exception e ) {
94+ logger .log (SentryLevel .WARNING , "Could not load " + className + "." + fieldName + " field" );
95+ }
96+ return null ;
97+ }
8098}
0 commit comments