Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
### Fixes

- Allow multiple UncaughtExceptionHandlerIntegrations to be active at the same time ([#4462](https://github.com/getsentry/sentry-java/pull/4462))
- Prevent repeated scroll target determination during a single scroll gesture ([#4557](https://github.com/getsentry/sentry-java/pull/4557))
- This should reduce the number of ANRs seen in `SentryGestureListener`

## 8.17.0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ public boolean onScroll(
options
.getLogger()
.log(SentryLevel.DEBUG, "Unable to find scroll target. No breadcrumb captured.");
scrollState.type = GestureType.Scroll;
Copy link

Copilot AI Jul 16, 2025

Choose a reason for hiding this comment

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

[nitpick] Setting scrollState.type to GestureType.Scroll when no scroll target is found may be misleading. Consider using a more descriptive state like GestureType.NoTarget or adding a comment explaining why Scroll is appropriate when no target exists.

Copilot uses AI. Check for mistakes.
return false;
} else {
options
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ import android.widget.AbsListView
import android.widget.ListAdapter
import androidx.core.view.ScrollingView
import io.sentry.Breadcrumb
import io.sentry.ILogger
import io.sentry.IScope
import io.sentry.IScopes
import io.sentry.PropagationContext
import io.sentry.Scope
import io.sentry.ScopeCallback
import io.sentry.SentryLevel
import io.sentry.SentryLevel.INFO
import io.sentry.android.core.SentryAndroidOptions
import kotlin.test.Test
Expand All @@ -28,6 +30,7 @@ import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.inOrder
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyNoMoreInteractions
import org.mockito.kotlin.whenever
Expand Down Expand Up @@ -56,7 +59,7 @@ class SentryGestureListenerScrollTest {
val directions = setOf("up", "down", "left", "right")

internal inline fun <reified T : View> getSut(
resourceName: String = "test_scroll_view",
resourceName: String? = "test_scroll_view",
touchWithinBounds: Boolean = true,
direction: String = "",
): SentryGestureListener {
Expand Down Expand Up @@ -229,6 +232,22 @@ class SentryGestureListenerScrollTest {
verify(fixture.scope).propagationContext = any()
}

@Test
fun `logs error message only once per gesture when no scroll target is found`() {
val logger = mock<ILogger>()
fixture.options.setLogger(logger)
fixture.options.isDebug = true
val sut = fixture.getSut<ScrollableListView>(resourceName = null)

sut.onDown(fixture.firstEvent)
fixture.eventsInBetween.forEach { sut.onScroll(fixture.firstEvent, it, 10.0f, 0f) }
sut.onUp(fixture.endEvent)

// Verify that the error message is logged only once during the entire gesture
verify(logger, times(1))
.log(SentryLevel.DEBUG, "Unable to find scroll target. No breadcrumb captured.")
}

internal class ScrollableView : View(mock()), ScrollingView {
override fun computeVerticalScrollOffset(): Int = 0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ internal inline fun <reified T : View> mockView(
return mockView
}

internal fun Resources.mockForTarget(target: View, expectedResourceName: String) {
whenever(getResourceEntryName(target.id)).thenReturn(expectedResourceName)
internal fun Resources.mockForTarget(target: View, expectedResourceName: String?) {
if (expectedResourceName == null) {
whenever(getResourceEntryName(target.id))
.thenThrow(Resources.NotFoundException("res not found"))
} else {
whenever(getResourceEntryName(target.id)).thenReturn(expectedResourceName)
}
}
Loading