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

Fix tags missing for compose view hierarchies #4275

Open
wants to merge 11 commits into
base: main
Choose a base branch
from

Conversation

markushi
Copy link
Member

📜 Description

Only on newer versions of Jetpack Compose

💡 Motivation and Context

Fixes #4273

💚 How did you test it?

📝 Checklist

  • I added tests to verify the changes.
  • No new PII added or SDK only sends newly added PII if sendDefaultPII is enabled.
  • I updated the docs if needed.
  • I updated the wizard if needed.
  • Review from the native team if needed.
  • No breaking change or entry added to the changelog.
  • No breaking change for hybrid SDKs or communicated to hybrid SDKs.

🔮 Next steps

Copy link
Contributor

github-actions bot commented Mar 21, 2025

Performance metrics 🚀

  Plain With Sentry Diff
Startup time 390.96 ms 418.91 ms 27.95 ms
Size 1.58 MiB 2.22 MiB 655.17 KiB

Previous results on branch: markushi/fix/compose-view-hierarchy-exporter

Startup times

Revision Plain With Sentry Diff
f63778b 413.88 ms 482.02 ms 68.14 ms
6f1848f 384.70 ms 441.52 ms 56.82 ms
eb39954 436.12 ms 493.80 ms 57.68 ms
b159a6d 378.41 ms 464.32 ms 85.91 ms

App size

Revision Plain With Sentry Diff
f63778b 1.58 MiB 2.22 MiB 652.84 KiB
6f1848f 1.58 MiB 2.22 MiB 652.90 KiB
eb39954 1.58 MiB 2.22 MiB 652.89 KiB
b159a6d 1.58 MiB 2.22 MiB 653.07 KiB

@@ -99,6 +105,21 @@ private static void setTag(
}
}
}
} else if ("androidx.compose.ui.platform.TestTagElement".equals(type)
Copy link
Member

Choose a reason for hiding this comment

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

is it possible to test it with robolectric/instrumented tests to avoid regressions in future?

Copy link
Member

@romtsn romtsn left a comment

Choose a reason for hiding this comment

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

would be nice to have tests for this (and extract that logic into a separate class if possible), but LGTM otherwise!

return value as String?
}
} catch (e: Throwable) {
// ignored
Copy link
Member

Choose a reason for hiding this comment

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

maybe worth to add a debug log here so we can troubleshoot easier?

fun getLayoutNodeBoundsInWindow(node: LayoutNode): Rect? {
if (layoutDelegateField != null) {
try {
val delegate =
Copy link
Member

Choose a reason for hiding this comment

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

hmm, I'm thinking you could directly access coordinates in LayoutNode node.coordinates.boundsInWindow() without reflection. At least that's what I'm doing in here:

val visibleRect = node.coordinates.boundsInWindow(_rootCoordinates?.get())

however I think it returns an innerCoordinator as opposed to outerCoordinator you use - do you know the difference between those? 😅

Copy link
Member

Choose a reason for hiding this comment

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

Also, I think you could make use of this vendored boundsInWindow method too right?

/**
* A faster copy of https://github.com/androidx/androidx/blob/fc7df0dd68466ac3bb16b1c79b7a73dd0bfdd4c1/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/LayoutCoordinates.kt#L187
*
* Since we traverse the tree from the root, we don't need to find it again from the leaf node and
* just pass it as an argument.
*
* @return boundaries of this layout relative to the window's origin.
*/
internal fun LayoutCoordinates.boundsInWindow(rootCoordinates: LayoutCoordinates?): Rect {
val root = rootCoordinates ?: findRootCoordinates()
val rootWidth = root.size.width.toFloat()
val rootHeight = root.size.height.toFloat()
val bounds = root.localBoundingBoxOf(this)
val boundsLeft = bounds.left.fastCoerceIn(0f, rootWidth)
val boundsTop = bounds.top.fastCoerceIn(0f, rootHeight)
val boundsRight = bounds.right.fastCoerceIn(0f, rootWidth)
val boundsBottom = bounds.bottom.fastCoerceIn(0f, rootHeight)
if (boundsLeft == boundsRight || boundsTop == boundsBottom) {
return Rect()
}
val topLeft = root.localToWindow(Offset(boundsLeft, boundsTop))
val topRight = root.localToWindow(Offset(boundsRight, boundsTop))
val bottomRight = root.localToWindow(Offset(boundsRight, boundsBottom))
val bottomLeft = root.localToWindow(Offset(boundsLeft, boundsBottom))
val topLeftX = topLeft.x
val topRightX = topRight.x
val bottomLeftX = bottomLeft.x
val bottomRightX = bottomRight.x
val left = fastMinOf(topLeftX, topRightX, bottomLeftX, bottomRightX)
val right = fastMaxOf(topLeftX, topRightX, bottomLeftX, bottomRightX)
val topLeftY = topLeft.y
val topRightY = topRight.y
val bottomLeftY = bottomLeft.y
val bottomRightY = bottomRight.y
val top = fastMinOf(topLeftY, topRightY, bottomLeftY, bottomRightY)
val bottom = fastMaxOf(topLeftY, topRightY, bottomLeftY, bottomRightY)
return Rect(left.toInt(), top.toInt(), right.toInt(), bottom.toInt())

You also have access to the root node (at least in the view hierarchy exporter), I think it would speed up things by quite a bit. Ideally we'd share that method via a separate module, but for simplicity I'd suggest just copying it over in this package for now to not deal with another module.

val nodeA = Mockito.mock(
LayoutNode::class.java
)
`when`(nodeA.isPlaced).thenReturn(isPlaced)
Copy link
Member

Choose a reason for hiding this comment

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

I guess we could use whenever from mockito-kotlin here

node: LayoutNode,
vhNode: ViewHierarchyNode
) {
// needs to be in-sync with ComposeGestureTargetLocator
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// needs to be in-sync with ComposeGestureTargetLocator

val nodeHeight = node.height
val nodeWidth = node.width

vhNode.height = nodeHeight.toDouble()
Copy link
Member

Choose a reason for hiding this comment

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

wondering if we could use bounds.bottom and bounds.right here? should be the same value (in theory 😅 )

Copy link
Member

@romtsn romtsn left a comment

Choose a reason for hiding this comment

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

LGTM again, but these two comments I guess would be nice to address: https://github.com/getsentry/sentry-java/pull/4275/files#r2014326540

Great tests btw, and very happy to get rid of that workaround with the java-only module

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

View Hierarchy for Compose testTag stopped working
3 participants