Skip to content

RUM-9298: Implement Compose actions tracking strategy#2586

Merged
ambushwork merged 1 commit into
feature/actions-trackingfrom
yl/track-actions/refactor-gesture-listener
Apr 10, 2025
Merged

RUM-9298: Implement Compose actions tracking strategy#2586
ambushwork merged 1 commit into
feature/actions-trackingfrom
yl/track-actions/refactor-gesture-listener

Conversation

@ambushwork

@ambushwork ambushwork commented Apr 7, 2025

Copy link
Copy Markdown
Member

What does this PR do?

Implement ComposeActionsTrackingStrategy to find the target of action in Jetpack Compose.

The main idea of the whole process:

  • GestureListener receives the motion event first and start to iterate the view tree from the decor view of the window.
  • During the iteration of the view tree we use android and compose strategy separately:
    • Use AndroidActionTrackingStrategy to simply test the view is clickable/scrollable and hit by the action.
    • Use ComposeActionTrackingStrategy to continue the iteration if the view is a Owner of compose layout node.
  • In ComposeActionTrackingStrategy, there is no more view tree, instead we can still iterate the layout node tree to find out which node is clickable/scrollable and hit by the motion event coordinates.

Motivation

RUM-9298

Additional Notes

Just in case of confusion, there are some back and forth from previous PR, the logic of iterating the view tree was moved out from GesturesListener to AndroidActionTrackingStrategy, now it is moved back to GestureListener, because before finding the compose view, we still need to iterate from DecorView to AndroidComposeView, so this part should always be the common logic between Android view and Jetpack Compose.

Review checklist (to be filled by reviewers)

  • Feature or bugfix MUST have appropriate tests (unit, integration, e2e)
  • Make sure you discussed the feature or bugfix with the maintaining team in an Issue
  • Make sure each commit and the PR mention the Issue number (cf the CONTRIBUTING doc)

@ambushwork
ambushwork force-pushed the yl/track-actions/refactor-gesture-listener branch 4 times, most recently from ae0908d to 486e6b7 Compare April 8, 2025 08:57
@codecov-commenter

codecov-commenter commented Apr 8, 2025

Copy link
Copy Markdown

Codecov Report

Attention: Patch coverage is 74.19355% with 40 lines in your changes missing coverage. Please review.

Project coverage is 70.16%. Comparing base (7a03965) to head (9543ce6).

Files with missing lines Patch % Lines
.../android/compose/internal/utils/LayoutNodeUtils.kt 53.66% 14 Missing and 5 partials ⚠️
.../compose/internal/ComposeActionTrackingStrategy.kt 46.88% 11 Missing and 6 partials ⚠️
...ernal/instrumentation/gestures/GesturesListener.kt 95.83% 1 Missing and 2 partials ⚠️
...internal/instrumentation/gestures/GesturesUtils.kt 66.67% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@                     Coverage Diff                      @@
##           feature/actions-tracking    #2586      +/-   ##
============================================================
+ Coverage                     70.15%   70.16%   +0.01%     
============================================================
  Files                           809      810       +1     
  Lines                         30245    30332      +87     
  Branches                       5053     5076      +23     
============================================================
+ Hits                          21218    21282      +64     
- Misses                         7622     7629       +7     
- Partials                       1405     1421      +16     
Files with missing lines Coverage Δ
...entation/gestures/AndroidActionTrackingStrategy.kt 95.00% <100.00%> (-1.43%) ⬇️
...com/datadog/android/compose/RumConfigurationExt.kt 0.00% <ø> (ø)
...internal/instrumentation/gestures/GesturesUtils.kt 85.71% <66.67%> (-5.19%) ⬇️
...ernal/instrumentation/gestures/GesturesListener.kt 94.41% <95.83%> (+1.95%) ⬆️
.../compose/internal/ComposeActionTrackingStrategy.kt 46.88% <46.88%> (ø)
.../android/compose/internal/utils/LayoutNodeUtils.kt 53.66% <53.66%> (ø)

... and 35 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ambushwork
ambushwork force-pushed the yl/track-actions/refactor-gesture-listener branch from 486e6b7 to b8e0d7c Compare April 8, 2025 09:46
@ambushwork
ambushwork marked this pull request as ready for review April 8, 2025 09:54
@ambushwork
ambushwork requested review from a team as code owners April 8, 2025 09:54
@ambushwork
ambushwork force-pushed the yl/track-actions/refactor-gesture-listener branch from b8e0d7c to 942c5b1 Compare April 8, 2025 13:58

@0xnm 0xnm left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nice work! I left some questions, bit overall lgtm.

Comment on lines +52 to +68
} catch (@Suppress("TooGenericExceptionCaught") e: Exception) {
// All exceptions should be caught and return in case of crash.
logAddQueueException(e)
// The iteration of the layout nodes tree has failed, return null.
return null
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

in which case we can end up here? like owner.root call can throw?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

mostly because queue.add can throw exception, also owner.root is an internal call, it can also throw runtime exception if there is any binary compatibility issue.

} catch (@Suppress("TooGenericExceptionCaught") e: Exception) {
// All exceptions should be caught and return in case of crash.
logAddQueueException(e)
// The iteration of the layout nodes tree has failed, return null.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

is it owner.root call who does the iteration under the hood?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

No, owner is ususally AndroidComposeView, which is the root container of the whole compose view, so owner.root just simply returns the root layout node of compose, and we do the iteration from this node with while loop just below.

}

private fun logAddQueueException(e: Exception) {
(Datadog.getInstance() as? FeatureSdkCore)?.internalLogger?.log(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We should use the instance for the feature. Or is it like that because of the initialization sequence?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

we have instance per feature? how to get it from here?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

yes, you normally have and instance of FeatureSdkCore passed during the RumFeature initialization, and this is the instance which should be used.

Is there a chance to get it here? But I understand, that it may be not available when we just create a configuration object.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

yes we create it when building the configuration, so we don't have the instance of FeatureSdkCore here

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

maybe it is worth then also implement com.datadog.android.rum.tracking.TrackingStrategy interface? There you can get SdkCore instance, you can check how ActivityLifecycleTrackingStrategy is using it, for example.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

updated, now I make ActionTrackingStrategy extend TrackingStrategy and the registration of sdkCore happens in GesturesListener

@ambushwork
ambushwork force-pushed the yl/track-actions/refactor-gesture-listener branch from 942c5b1 to 06cd701 Compare April 9, 2025 13:24
@ambushwork
ambushwork requested a review from 0xnm April 9, 2025 14:28
@ambushwork
ambushwork force-pushed the yl/track-actions/refactor-gesture-listener branch from 06cd701 to b997aa7 Compare April 10, 2025 07:59
0xnm
0xnm previously approved these changes Apr 10, 2025
Comment on lines +53 to +54
composeActionTrackingStrategy.register(sdkCore, it)
androidActionTrackingStrategy.register(sdkCore, it)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

normally we need to unregister as well, but I guess if SDK core is stopped, then it won't be referenced from user code and even though it will have a reference from action tracking strategies, it will be still deallocated.

* 3(c) 4
*/
@Test
fun `M return lowest level clickable node W findTargetForTap`(forge: Forge) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

minor: let's use bottom-most instead of lowest level, seems like it is the opposite of the quite common top-most.

* 3(c) 4
*/
@Test
fun `M return most inisde scrollable node W findTargetForScroll`(forge: Forge) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
fun `M return most inisde scrollable node W findTargetForScroll`(forge: Forge) {
fun `M return bottom-most scrollable node W findTargetForScroll`(forge: Forge) {

@ambushwork
ambushwork force-pushed the yl/track-actions/refactor-gesture-listener branch from b997aa7 to 9543ce6 Compare April 10, 2025 09:20
@ambushwork
ambushwork requested a review from 0xnm April 10, 2025 12:25
@ambushwork
ambushwork merged commit b97cbe1 into feature/actions-tracking Apr 10, 2025
@ambushwork
ambushwork deleted the yl/track-actions/refactor-gesture-listener branch April 10, 2025 12:51
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.

3 participants