From 3fa001fcc0bffaf600f6125a9a118102c0ac485d Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Tue, 27 Oct 2026 15:54:55 +0100 Subject: [PATCH] fix(sample): Drop Nav3 Activity ui.load transactions when the toggle is off The sample cancelled the Activity's ui.load transaction through the scope. When another transaction was already bound, such as the previous Activity's nav transaction, the SDK didn't bind ui.load, so the cancel missed and the transaction was still sent, often waiting the full deadline with the Activity's spans. Keep cancelling a bound ui.load transaction so it doesn't block the first nav transaction, and also drop Nav3 sample Activities' ui.load transactions in an event processor when the Activity was created with the toggle off. Co-Authored-By: Claude Opus 5.5 --- .../android/navigation/nav3/Nav3Activity.kt | 9 ++-- .../navigation/nav3/Nav3MultiActivity.kt | 8 +--- .../navigation/nav3/Nav3MultiFragment.kt | 4 +- .../navigation/nav3/Nav3SampleTracing.kt | 48 +++++++++++++++++++ 4 files changed, 55 insertions(+), 14 deletions(-) diff --git a/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/navigation/nav3/Nav3Activity.kt b/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/navigation/nav3/Nav3Activity.kt index e90e27573a..449ac2b299 100644 --- a/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/navigation/nav3/Nav3Activity.kt +++ b/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/navigation/nav3/Nav3Activity.kt @@ -92,11 +92,10 @@ class Nav3Activity : ComponentActivity() { configuration.applyToCurrentOptions() showActivityUiLoadTransactionDelayMessage = configuration.hasOnlyActivityUiLoadTransactions - if (!configuration.enableActivityUiLoadTransaction) { - // Cancel the already-started Activity transaction before Compose installs SentryNavEffect, - // otherwise the initial /Landing route transaction gets preempted by the lingering ui.load. - cancelCurrentActivityUiLoadTransaction() - } + // When the toggle is off, cancel the already-started Activity transaction before Compose + // installs SentryNavEffect, otherwise the lingering ui.load preempts the initial /Landing route + // transaction. + applyActivityUiLoadTransactionSetting(this, configuration.enableActivityUiLoadTransaction) NavigationTransactionHistory.install() diff --git a/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/navigation/nav3/Nav3MultiActivity.kt b/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/navigation/nav3/Nav3MultiActivity.kt index 5e6ca0b9d0..e3c92bf8d7 100644 --- a/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/navigation/nav3/Nav3MultiActivity.kt +++ b/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/navigation/nav3/Nav3MultiActivity.kt @@ -89,9 +89,7 @@ class Nav3ProductsActivity : ComponentActivity() { super.onCreate(savedInstanceState) val configuration = (intent.extras ?: Bundle()).nav3SampleConfiguration() NavigationTransactionHistory.install() - if (!configuration.enableActivityUiLoadTransaction) { - cancelCurrentActivityUiLoadTransaction() - } + applyActivityUiLoadTransactionSetting(this, configuration.enableActivityUiLoadTransaction) setContent { val stack = rememberSaveableNav3BackStack(Nav3Route.ProductList) SideEffect { @@ -174,9 +172,7 @@ open class Nav3CheckoutActivity : ComponentActivity() { val crashOnFirstComposition = savedInstanceState == null && intent.getBooleanExtra(EXTRA_CRASH_ON_FIRST_COMPOSITION, false) NavigationTransactionHistory.install() - if (!configuration.enableActivityUiLoadTransaction) { - cancelCurrentActivityUiLoadTransaction() - } + applyActivityUiLoadTransactionSetting(this, configuration.enableActivityUiLoadTransaction) setContent { Nav3SampleTheme { if (showAsSheet) { diff --git a/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/navigation/nav3/Nav3MultiFragment.kt b/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/navigation/nav3/Nav3MultiFragment.kt index 846344103c..d95be37b4a 100644 --- a/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/navigation/nav3/Nav3MultiFragment.kt +++ b/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/navigation/nav3/Nav3MultiFragment.kt @@ -76,9 +76,7 @@ class Nav3FragmentsActivity : FragmentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) NavigationTransactionHistory.install() - if (!configuration.enableActivityUiLoadTransaction) { - cancelCurrentActivityUiLoadTransaction() - } + applyActivityUiLoadTransactionSetting(this, configuration.enableActivityUiLoadTransaction) val controls = ComposeView(this).apply { diff --git a/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/navigation/nav3/Nav3SampleTracing.kt b/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/navigation/nav3/Nav3SampleTracing.kt index a23ed37c3f..9b266f52b9 100644 --- a/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/navigation/nav3/Nav3SampleTracing.kt +++ b/sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/navigation/nav3/Nav3SampleTracing.kt @@ -1,8 +1,12 @@ package io.sentry.samples.android.navigation.nav3 +import android.app.Activity +import io.sentry.EventProcessor +import io.sentry.Hint import io.sentry.Sentry import io.sentry.SpanStatus import io.sentry.protocol.SentryTransaction +import java.util.concurrent.ConcurrentHashMap import kotlin.coroutines.resume import kotlinx.coroutines.suspendCancellableCoroutine @@ -21,6 +25,50 @@ internal fun SentryTransaction.navigationSampleScenarioLabel(): String = ?: getTag(NAV3_SCENARIO_TAG) ?: UNKNOWN_NAVIGATION_SCENARIO_LABEL +/** + * Applies the sample's "Activity ui.load transaction" toggle to [activity]. + * + * Call from `onCreate` after `super.onCreate`, once the SDK has started the Activity's ui.load + * transaction. When [enabled] is false, the transaction is cancelled if it's bound to the scope. + * It's also dropped before sending, because the SDK doesn't bind it when another transaction (such + * as the previous Activity's nav transaction) is already bound, and the sample can't reach an + * unbound ui.load transaction to cancel it. + */ +internal fun applyActivityUiLoadTransactionSetting(activity: Activity, enabled: Boolean) { + // Matches the transaction name ActivityLifecycleIntegration uses. + val activityName = activity.javaClass.simpleName + if (enabled) { + SuppressedActivityUiLoadTransactions.activityNames.remove(activityName) + return + } + SuppressedActivityUiLoadTransactions.install() + SuppressedActivityUiLoadTransactions.activityNames.add(activityName) + cancelCurrentActivityUiLoadTransaction() +} + +/** Drops ui.load transactions for sample Activities created with the ui.load toggle off. */ +private object SuppressedActivityUiLoadTransactions : EventProcessor { + val activityNames: MutableSet = ConcurrentHashMap.newKeySet() + + @Volatile private var installed = false + + fun install() { + if (installed) return + synchronized(this) { + if (installed) return + Sentry.getCurrentScopes().options.addEventProcessor(this) + installed = true + } + } + + override fun process(transaction: SentryTransaction, hint: Hint): SentryTransaction? { + val isSuppressed = + transaction.contexts.trace?.operation == ACTIVITY_UI_LOAD_OP && + transaction.transaction in activityNames + return if (isSuppressed) null else transaction + } +} + internal fun cancelCurrentActivityUiLoadTransaction() { Sentry.configureScope { scope -> scope.withTransaction { transaction ->