@@ -10,6 +10,12 @@ package com.datadog.android.compose
1010import androidx.compose.runtime.Composable
1111import androidx.compose.runtime.LaunchedEffect
1212import androidx.compose.runtime.NonRestartableComposable
13+ import androidx.compose.runtime.State
14+ import androidx.compose.runtime.getValue
15+ import androidx.compose.runtime.produceState
16+ import androidx.compose.ui.platform.LocalLifecycleOwner
17+ import androidx.lifecycle.Lifecycle
18+ import androidx.lifecycle.LifecycleEventObserver
1319import com.datadog.android.Datadog
1420import com.datadog.android.api.InternalLogger
1521import com.datadog.android.api.SdkCore
@@ -19,27 +25,29 @@ import com.datadog.android.compose.internal.SupportLibrary
1925import com.datadog.android.compose.internal.sendTelemetry
2026import com.datadog.android.internal.attributes.ViewScopeInstrumentationType
2127import com.datadog.android.internal.attributes.enrichWithConstantAttribute
22- import com.datadog.android.rum.ExperimentalRumApi
2328import com.datadog.android.rum.GlobalRumMonitor
29+ import com.datadog.android.rum.RumMonitor
2430import com.datadog.android.rum.tracking.ComponentPredicate
2531
2632/* *
2733 * A side effect which should be used to track view navigation with the Navigation3
2834 * for Jetpack Compose setup.
2935 *
3036 * @param T the type of the key of navigation back stack.
31- * @param backStack backStack of the navigation to watch.
32- * @param keyPredicate to accept the backstack key that will be taken into account as
37+ * @param backStack back stack of the navigation to watch.
38+ * @param keyPredicate to accept the back stack key that will be taken into account as
3339 * valid RUM View events.
34- * @param attributesResolver to resolve attributes for the current backstack key.
40+ * @param backStackKeyResolver to resolve stable keys for the back stack keys.
41+ * @param attributesResolver to resolve attributes for the current back stack key.
3542 * @param sdkCore the SDK instance to use. If not provided, default instance will be used.
3643 */
3744@Composable
3845@NonRestartableComposable
39- @ExperimentalRumApi
40- fun <T > Navigation3TrackingEffect (
46+ @ExperimentalTrackingApi
47+ fun <T : Any > Navigation3TrackingEffect (
4148 backStack : List <T >,
4249 keyPredicate : ComponentPredicate <T > = AcceptAllNavKeyPredicate (),
50+ backStackKeyResolver : BackStackKeyResolver <T > = HashcodeBackStackKeyResolver (),
4351 attributesResolver : AttributesResolver <T >? = null,
4452 sdkCore : SdkCore = Datadog .getInstance()
4553) {
@@ -54,16 +62,18 @@ fun <T> Navigation3TrackingEffect(
5462 InternalNavigation3TrackingStrategy (
5563 backStack = backStack,
5664 destinationPredicate = keyPredicate,
65+ backStackKeyResolver = backStackKeyResolver,
5766 attributesResolver = attributesResolver,
5867 sdkCore = sdkCore
5968 )
6069}
6170
6271@Composable
6372@NonRestartableComposable
64- internal fun <T > InstrumentedNavigation3TrackingEffect (
73+ internal fun <T : Any > InstrumentedNavigation3TrackingEffect (
6574 backStack : List <T >,
6675 keyPredicate : ComponentPredicate <T > = AcceptAllNavKeyPredicate (),
76+ backStackKeyResolver : BackStackKeyResolver <T > = HashcodeBackStackKeyResolver (),
6777 attributesResolver : AttributesResolver <T >? = null,
6878 sdkCore : SdkCore = Datadog .getInstance()
6979) {
@@ -79,42 +89,95 @@ internal fun <T> InstrumentedNavigation3TrackingEffect(
7989 backStack = backStack,
8090 destinationPredicate = keyPredicate,
8191 attributesResolver = attributesResolver,
92+ backStackKeyResolver = backStackKeyResolver,
8293 sdkCore = sdkCore
8394 )
8495}
8596
8697@Composable
8798@NonRestartableComposable
88- private fun <T > InternalNavigation3TrackingStrategy (
99+ private fun <T : Any > InternalNavigation3TrackingStrategy (
89100 backStack : List <T >,
90101 destinationPredicate : ComponentPredicate <T > = AcceptAllNavKeyPredicate (),
102+ backStackKeyResolver : BackStackKeyResolver <T > = HashcodeBackStackKeyResolver (),
91103 attributesResolver : AttributesResolver <T >? = null,
92104 sdkCore : SdkCore = Datadog .getInstance()
93105) {
106+ val topKey = backStack.lastOrNull() ? : return
107+ val isResumed by rememberIsResumed()
94108 val internalLogger = (sdkCore as ? FeatureSdkCore )?.internalLogger ? : InternalLogger .UNBOUND
95- val topKey = backStack.lastOrNull()
96- LaunchedEffect (topKey) {
97- topKey?.takeIf { destinationPredicate.accept(it) }?.let { current ->
98- try {
109+ LaunchedEffect (isResumed, topKey) {
110+ trackBackStack(
111+ topKey = topKey,
112+ isResumed = isResumed,
113+ keyPredicate = destinationPredicate,
114+ backStackKeyResolver = backStackKeyResolver,
115+ attributesResolver = attributesResolver,
116+ rumMonitor = GlobalRumMonitor .get(sdkCore),
117+ internalLogger = internalLogger
118+ )
119+ }
120+ }
121+
122+ internal fun <T : Any > trackBackStack (
123+ topKey : T ,
124+ isResumed : Boolean ,
125+ keyPredicate : ComponentPredicate <T > = AcceptAllNavKeyPredicate (),
126+ backStackKeyResolver : BackStackKeyResolver <T >,
127+ attributesResolver : AttributesResolver <T >? = null,
128+ rumMonitor : RumMonitor ,
129+ internalLogger : InternalLogger
130+ ) {
131+ val viewKey = backStackKeyResolver.getStableKey(topKey)
132+ if (isResumed) {
133+ try {
134+ if (keyPredicate.accept(topKey)) {
99135 val attributes =
100- attributesResolver?.resolveAttributes(current )?.toMutableMap()
136+ attributesResolver?.resolveAttributes(topKey )?.toMutableMap()
101137 ?.enrichWithConstantAttribute(ViewScopeInstrumentationType .COMPOSE )
102138 ? : emptyMap()
103139
104- GlobalRumMonitor .get(sdkCore) .startView(
105- name = destinationPredicate .getViewName(current )
106- ? : resolveDefaultViewName(current ),
107- key = current.toString() ,
140+ rumMonitor .startView(
141+ name = keyPredicate .getViewName(topKey )
142+ ? : resolveDefaultViewName(topKey ),
143+ key = viewKey ,
108144 attributes = attributes
109145 )
110- } catch (@Suppress( " TooGenericExceptionCaught " ) e : Exception ) {
146+ } else {
111147 internalLogger.log(
112- InternalLogger .Level .ERROR ,
113- listOf (InternalLogger .Target .MAINTAINER , InternalLogger .Target .TELEMETRY ),
114- { " Internal operation failed on ComponentPredicate" },
115- e
148+ InternalLogger .Level .DEBUG ,
149+ InternalLogger .Target .USER ,
150+ { " The provided keyPredicate did not accept the back stack top key: $topKey " }
116151 )
117152 }
153+ } catch (@Suppress(" TooGenericExceptionCaught" ) e: Exception ) {
154+ internalLogger.log(
155+ InternalLogger .Level .ERROR ,
156+ listOf (InternalLogger .Target .MAINTAINER , InternalLogger .Target .TELEMETRY ),
157+ { " Internal operation failed on ComponentPredicate" },
158+ e
159+ )
160+ }
161+ } else {
162+ rumMonitor.stopView(viewKey)
163+ }
164+ }
165+
166+ @Composable
167+ private fun rememberIsResumed (): State <Boolean > {
168+ val lifecycle = LocalLifecycleOwner .current.lifecycle
169+ return produceState(
170+ initialValue = lifecycle.currentState.isAtLeast(Lifecycle .State .RESUMED ),
171+ key1 = lifecycle
172+ ) {
173+ val observer = LifecycleEventObserver { _, _ ->
174+ value = lifecycle.currentState.isAtLeast(Lifecycle .State .RESUMED )
175+ }
176+ @Suppress(" ThreadSafety" ) // TODO RUM-525 check composable threading rules
177+ lifecycle.addObserver(observer)
178+ awaitDispose {
179+ @Suppress(" ThreadSafety" ) // TODO RUM-525 check composable threading rules
180+ lifecycle.removeObserver(observer)
118181 }
119182 }
120183}
0 commit comments