Skip to content

Commit bfe02ba

Browse files
committed
fix(android): Prevent repeated scroll target logging by updating scrollState.type
When ViewUtils.findTarget returns null in SentryGestureListener.onScroll, the code was logging an error but not updating scrollState.type from Unknown. This caused repeated target searches and duplicate log messages on subsequent onScroll calls during the same gesture. The fix sets scrollState.type = GestureType.Scroll even when target is null, preventing repeated search attempts while maintaining existing behavior. Fixes: "Unable to find scroll target. No breadcrumb captured." being logged repeatedly
1 parent 2bfacef commit bfe02ba

File tree

2 files changed

+48
-0
lines changed

2 files changed

+48
-0
lines changed

sentry-android-core/src/main/java/io/sentry/android/core/internal/gestures/SentryGestureListener.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ public boolean onScroll(
139139
options
140140
.getLogger()
141141
.log(SentryLevel.DEBUG, "Unable to find scroll target. No breadcrumb captured.");
142+
scrollState.type = GestureType.Scroll;
142143
return false;
143144
} else {
144145
options

sentry-android-core/src/test/java/io/sentry/android/core/internal/gestures/SentryGestureListenerScrollTest.kt

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@ import android.widget.AbsListView
1111
import android.widget.ListAdapter
1212
import androidx.core.view.ScrollingView
1313
import io.sentry.Breadcrumb
14+
import io.sentry.ILogger
1415
import io.sentry.IScope
1516
import io.sentry.IScopes
1617
import io.sentry.PropagationContext
1718
import io.sentry.Scope
1819
import io.sentry.ScopeCallback
20+
import io.sentry.SentryLevel
1921
import io.sentry.SentryLevel.INFO
2022
import io.sentry.android.core.SentryAndroidOptions
2123
import kotlin.test.Test
@@ -28,6 +30,7 @@ import org.mockito.kotlin.doAnswer
2830
import org.mockito.kotlin.inOrder
2931
import org.mockito.kotlin.mock
3032
import org.mockito.kotlin.never
33+
import org.mockito.kotlin.times
3134
import org.mockito.kotlin.verify
3235
import org.mockito.kotlin.verifyNoMoreInteractions
3336
import org.mockito.kotlin.whenever
@@ -229,6 +232,50 @@ class SentryGestureListenerScrollTest {
229232
verify(fixture.scope).propagationContext = any()
230233
}
231234

235+
@Test
236+
fun `logs error message only once per gesture when no scroll target is found`() {
237+
val mockLogger = mock<ILogger>()
238+
fixture.options.setLogger(mockLogger)
239+
240+
// Create a setup where no scrollable view is found
241+
// Use regular View which doesn't implement ScrollingView/AbsListView/ScrollView
242+
fixture.target =
243+
mockView<View>(
244+
event = fixture.firstEvent,
245+
touchWithinBounds = true,
246+
context = fixture.context,
247+
)
248+
fixture.window.mockDecorView<ViewGroup>(event = fixture.firstEvent) {
249+
whenever(it.childCount).thenReturn(1)
250+
whenever(it.getChildAt(0)).thenReturn(fixture.target)
251+
}
252+
253+
fixture.resources.mockForTarget(fixture.target, "test_view")
254+
whenever(fixture.context.resources).thenReturn(fixture.resources)
255+
whenever(fixture.target.context).thenReturn(fixture.context)
256+
whenever(fixture.activity.window).thenReturn(fixture.window)
257+
doAnswer { (it.arguments[0] as ScopeCallback).run(fixture.scope) }
258+
.whenever(fixture.scopes)
259+
.configureScope(any())
260+
doAnswer {
261+
(it.arguments[0] as Scope.IWithPropagationContext).accept(fixture.propagationContext)
262+
fixture.propagationContext
263+
}
264+
.whenever(fixture.scope)
265+
.withPropagationContext(any())
266+
267+
val sut = SentryGestureListener(fixture.activity, fixture.scopes, fixture.options)
268+
269+
sut.onDown(fixture.firstEvent)
270+
// Multiple onScroll calls during the same gesture - should only log once
271+
fixture.eventsInBetween.forEach { sut.onScroll(fixture.firstEvent, it, 10f, 0f) }
272+
sut.onUp(fixture.endEvent)
273+
274+
// Verify that the error message is logged only once during the entire gesture
275+
verify(mockLogger, times(1))
276+
.log(SentryLevel.DEBUG, "Unable to find scroll target. No breadcrumb captured.")
277+
}
278+
232279
internal class ScrollableView : View(mock()), ScrollingView {
233280
override fun computeVerticalScrollOffset(): Int = 0
234281

0 commit comments

Comments
 (0)