Skip to content

Commit 291380e

Browse files
committed
fix(android-nav3): Preserve app context when updating transaction context
1 parent 6a2c118 commit 291380e

2 files changed

Lines changed: 51 additions & 23 deletions

File tree

‎sentry-android-navigation3/src/main/kotlin/io/sentry/compose/navigation3/BackStackObserver.kt‎

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ internal class BackStackObserver<T : Any>(
208208
currentTopRoute.name,
209209
currentTopRoute.arguments,
210210
)
211-
?.updateNavigationContext(currentBackStack)
211+
?.updateNavigationContext(scope, currentBackStack)
212212
} else {
213213
// Rotate the propagation context.
214214
scope.withPropagationContext { scope.setPropagationContext(PropagationContext()) }
@@ -255,9 +255,13 @@ internal class BackStackObserver<T : Any>(
255255
* past what's relevant to a given transaction. This method prevents misassociation by binding
256256
* proper values to the transaction context instead.
257257
*/
258-
private fun ITransaction.updateNavigationContext(backStack: BackStackData<T>) {
259-
val appContext = contexts.app ?: App().also { contexts.setApp(it) }
260-
appContext.viewNames = listOf(backStack.topRoute.name)
258+
private fun ITransaction.updateNavigationContext(scope: IScope, backStack: BackStackData<T>) {
259+
if (scopes.options.isEnableScreenTracking) {
260+
val appContext = contexts.app ?: io.sentry.protocol.Contexts(scope.contexts).app ?: App()
261+
262+
appContext.viewNames = listOf(backStack.topRoute.name)
263+
contexts.setApp(appContext)
264+
}
261265

262266
if (options.captureBackStack && backStack.capturedRoutes.isNotEmpty()) {
263267
setContext(NAVIGATION_CONTEXT_KEY, backStack.capturedRoutes.toNavigationContext())
@@ -303,6 +307,7 @@ internal class BackStackObserver<T : Any>(
303307
try {
304308
body()
305309
} catch (t: Throwable) {
310+
// Nav instrumentation can invoke host code through route translation and scope mutation.
306311
ExceptionUtils.rethrowIfFatal(t)
307312
scopes.options.logger.log(
308313
ERROR,

‎sentry-android-navigation3/src/test/kotlin/io/sentry/compose/navigation3/BackStackObserverTest.kt‎

Lines changed: 42 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import io.sentry.SentryTracer
1515
import io.sentry.TransactionContext
1616
import io.sentry.TransactionOptions
1717
import io.sentry.TypeCheckHint
18+
import io.sentry.protocol.App
1819
import io.sentry.protocol.TransactionNameSource
1920
import kotlin.test.Test
2021
import kotlin.test.assertNull
@@ -230,6 +231,7 @@ class BackStackObserverTest {
230231

231232
assertThat(fixture.scope.screen).isNull()
232233
assertThat(fixture.scope.contexts.app?.viewNames).isNull()
234+
assertThat(fixture.startedTransactions.single().contexts.app?.viewNames).isNull()
233235
}
234236

235237
@Test
@@ -372,6 +374,31 @@ class BackStackObserverTest {
372374
assertThat(argumentCalls).doesNotContainKey(home)
373375
}
374376

377+
@Test
378+
fun `onBackStackChanged resolves top entry arguments once per update`() {
379+
val fixture = Fixture()
380+
val home = HomeRoute()
381+
val profile = ProfileRoute("123")
382+
val argumentCalls = mutableMapOf<Any, Int>()
383+
val sut =
384+
fixture.getSut(
385+
argumentsExtractor =
386+
RouteArgumentsExtractor { entry ->
387+
argumentCalls[entry] = (argumentCalls[entry] ?: 0) + 1
388+
when (entry) {
389+
is HomeRoute -> mapOf("tab" to entry.id)
390+
is ProfileRoute -> mapOf("userId" to entry.userId)
391+
else -> emptyMap()
392+
}
393+
}
394+
)
395+
396+
sut.onBackStackChanged(listOf(home, profile))
397+
398+
assertThat(argumentCalls[profile]).isEqualTo(1)
399+
assertThat(argumentCalls[home]).isEqualTo(1)
400+
}
401+
375402
@Test
376403
fun `onBackStackChanged creates a nav transaction when enabled and no ambient transaction is active`() {
377404
val fixture = Fixture()
@@ -408,28 +435,24 @@ class BackStackObserverTest {
408435
}
409436

410437
@Test
411-
fun `onBackStackChanged resolves top entry arguments once per update`() {
438+
fun `onBackStackChanged preserves scope app fields on the nav transaction`() {
412439
val fixture = Fixture()
413-
val home = HomeRoute()
414-
val profile = ProfileRoute("123")
415-
val argumentCalls = mutableMapOf<Any, Int>()
416-
val sut =
417-
fixture.getSut(
418-
argumentsExtractor =
419-
RouteArgumentsExtractor { entry ->
420-
argumentCalls[entry] = (argumentCalls[entry] ?: 0) + 1
421-
when (entry) {
422-
is HomeRoute -> mapOf("tab" to entry.id)
423-
is ProfileRoute -> mapOf("userId" to entry.userId)
424-
else -> emptyMap()
425-
}
426-
}
427-
)
440+
val scopeApp =
441+
App().apply {
442+
appName = "Demo App"
443+
appIdentifier = "io.sentry.demo"
444+
}
445+
fixture.scope.contexts.setApp(scopeApp)
446+
val sut = fixture.getSut(config = ObserverConfig(enableScreenTracking = true))
428447

429-
sut.onBackStackChanged(listOf(home, profile))
448+
sut.onBackStackChanged(listOf(HomeRoute(), ProfileRoute("123")))
430449

431-
assertThat(argumentCalls[profile]).isEqualTo(1)
432-
assertThat(argumentCalls[home]).isEqualTo(1)
450+
val transactionApp = fixture.startedTransactions.single().contexts.app
451+
assertThat(transactionApp).isNotNull()
452+
assertThat(transactionApp).isNotSameInstanceAs(scopeApp)
453+
assertThat(transactionApp?.appName).isEqualTo("Demo App")
454+
assertThat(transactionApp?.appIdentifier).isEqualTo("io.sentry.demo")
455+
assertThat(transactionApp?.viewNames).isEqualTo(listOf("/ProfileRoute"))
433456
}
434457

435458
@Test

0 commit comments

Comments
 (0)