-
Notifications
You must be signed in to change notification settings - Fork 59
/
Navigation.kt
145 lines (127 loc) · 5.08 KB
/
Navigation.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
/*
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
* This product includes software developed at Datadog (https://www.datadoghq.com/).
* Copyright 2016-Present Datadog, Inc.
*/
@file:Suppress("MatchingDeclarationName")
package com.datadog.android.compose
import android.os.Bundle
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.NonRestartableComposable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner
import androidx.navigation.NavController
import androidx.navigation.NavDestination
import com.datadog.android.Datadog
import com.datadog.android.api.SdkCore
import com.datadog.android.rum.GlobalRumMonitor
import com.datadog.android.rum.RumMonitor
import com.datadog.android.rum.tracking.AcceptAllNavDestinations
import com.datadog.android.rum.tracking.ComponentPredicate
internal class ComposeNavigationObserver(
private val trackArguments: Boolean = true,
private val destinationPredicate: ComponentPredicate<NavDestination> =
AcceptAllNavDestinations(),
private val navController: NavController,
private val rumMonitor: RumMonitor
) : LifecycleEventObserver, NavController.OnDestinationChangedListener {
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
if (event == Lifecycle.Event.ON_RESUME) {
// once listener is added, it will receive current destination if available
navController.addOnDestinationChangedListener(this)
} else if (event == Lifecycle.Event.ON_PAUSE) {
navController.currentDestination?.route?.let {
rumMonitor.stopView(it)
}
navController.removeOnDestinationChangedListener(this)
}
}
override fun onDestinationChanged(
controller: NavController,
destination: NavDestination,
arguments: Bundle?
) {
if (destinationPredicate.accept(destination)) {
destination.route?.let {
startView(destination, it, arguments)
}
}
}
internal fun onDispose() {
// just a safe-guard if ON_PAUSE wasn't called
navController.removeOnDestinationChangedListener(this)
}
private fun startView(
navDestination: NavDestination,
route: String,
arguments: Bundle?
) {
val viewName = destinationPredicate.getViewName(navDestination) ?: route
rumMonitor.startView(
key = route,
name = viewName,
attributes = if (trackArguments) {
convertToRumAttributes(arguments)
} else {
emptyMap()
}
)
}
private fun convertToRumAttributes(bundle: Bundle?): Map<String, Any?> {
if (bundle == null) return emptyMap()
val attributes = mutableMapOf<String, Any?>()
bundle.keySet().forEach {
// TODO RUMM-2717 Bundle#get is deprecated, but there is no replacement for it.
// Issue is opened in the Google Issue Tracker.
@Suppress("DEPRECATION")
attributes["$ARGUMENT_TAG.$it"] = bundle.get(it)
}
return attributes
}
companion object {
private const val ARGUMENT_TAG: String = "view.arguments"
}
}
/**
* A side effect which should be used to track view navigation with the Navigation
* for Compose setup.
*
* @param navController [NavController] to watch
* @param trackArguments whether to track navigation arguments
* @param destinationPredicate to accept the [NavDestination] that will be taken into account as
* valid RUM View events.
* @param sdkCore the SDK instance to use. If not provided, default instance will be used.
*/
@ExperimentalTrackingApi
@Composable
@NonRestartableComposable
fun NavigationViewTrackingEffect(
navController: NavController,
trackArguments: Boolean = true,
destinationPredicate: ComponentPredicate<NavDestination> = AcceptAllNavDestinations(),
sdkCore: SdkCore = Datadog.getInstance()
) {
val currentTrackArguments by rememberUpdatedState(newValue = trackArguments)
val currentDestinationPredicate by rememberUpdatedState(newValue = destinationPredicate)
val lifecycleOwner = LocalLifecycleOwner.current
DisposableEffect(lifecycleOwner, navController) {
val observer = ComposeNavigationObserver(
currentTrackArguments,
currentDestinationPredicate,
navController,
GlobalRumMonitor.get(sdkCore)
)
@Suppress("ThreadSafety") // TODO RUMM-2214 check composable threading rules
lifecycleOwner.lifecycle.addObserver(observer)
onDispose {
@Suppress("ThreadSafety") // TODO RUMM-2214 check composable threading rules
lifecycleOwner.lifecycle.removeObserver(observer)
observer.onDispose()
}
}
}