Skip to content

Commit 5c8e9cf

Browse files
committed
ref(android): [Nav3 Lifecycle 2] Add lease-aware navigation scopes
Stage navigation scope state for inactive roots and publish it when the root acquires the lifecycle lease. Refs JAVA-274
1 parent 8292382 commit 5c8e9cf

7 files changed

Lines changed: 1355 additions & 0 deletions

File tree

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
package io.sentry.compose.navigation3.lifecycle
2+
3+
import io.sentry.IScope
4+
import io.sentry.ITransaction
5+
import io.sentry.PropagationContext
6+
import io.sentry.protocol.App
7+
import io.sentry.protocol.Contexts
8+
9+
/**
10+
* An [INavScope] implementation that writes to and reads from [delegate] only when [owner] holds
11+
* the nav lease; otherwise it falls back to [stagedNavData].
12+
*/
13+
internal class NavScope(
14+
private val delegate: IScope,
15+
private val stagedNavData: StagedNavData,
16+
private val coordinator: NavLeaseCoordinator,
17+
private val owner: Any,
18+
) : INavScope {
19+
20+
private val hasLease: Boolean
21+
get() = coordinator.hasLease(owner)
22+
23+
override var screen: String?
24+
get() = if (hasLease) delegate.screen else stagedNavData.screen
25+
set(value) {
26+
stagedNavData.stageScreen(value)
27+
if (hasLease) {
28+
stagedNavData.publishScreen(delegate)
29+
}
30+
}
31+
32+
override fun setNavigationContext(value: Any?) {
33+
stagedNavData.stageNavigationContext(value)
34+
if (hasLease) {
35+
stagedNavData.publishNavigationContext(delegate)
36+
}
37+
}
38+
39+
override val app: App?
40+
get() = Contexts(delegate.contexts).app
41+
42+
/**
43+
* The transaction bound to the scope. Without the lease, another owner's nav transaction isn't
44+
* reported, so [owner]'s attempt to start a nav transaction reaches [NavScopes.startTransaction],
45+
* which declines it and defers the propagation context rotation it implies. Non-nav transactions
46+
* (e.g., the host's) are always reported, so they still block nav transactions as usual.
47+
*/
48+
override val transaction: ITransaction?
49+
get() =
50+
delegate.transaction?.takeUnless { current ->
51+
!hasLease && coordinator.isNavTransaction(current)
52+
}
53+
54+
override fun bindTransaction(transaction: ITransaction) {
55+
if (!hasLease) {
56+
return
57+
}
58+
delegate.withTransaction { current ->
59+
if (current == null) {
60+
delegate.transaction = transaction
61+
}
62+
}
63+
}
64+
65+
/**
66+
* Unbinds [transaction] if it's bound, and, with or without the lease, tells the [coordinator] to
67+
* stop retaining it, so a retired transaction isn't restored when [owner] regains the lease.
68+
*/
69+
override fun unbindTransaction(transaction: ITransaction) {
70+
delegate.withTransaction { current ->
71+
if (current === transaction) {
72+
delegate.clearTransaction()
73+
}
74+
}
75+
coordinator.releaseTransaction(owner, transaction)
76+
}
77+
78+
override fun clearFinishedTransaction() {
79+
if (!hasLease) {
80+
return
81+
}
82+
delegate.withTransaction { current ->
83+
if (current?.isFinished == true) {
84+
delegate.clearTransaction()
85+
}
86+
}
87+
}
88+
89+
override fun rotatePropagationContext() {
90+
if (hasLease) {
91+
delegate.withPropagationContext { delegate.propagationContext = PropagationContext() }
92+
} else {
93+
stagedNavData.deferPropagationContextRotation()
94+
}
95+
}
96+
}
97+
98+
/**
99+
* The subset of the [IScope] interface used to record navigation data.
100+
*
101+
* Protects against accidental misuse of what would otherwise be a partially-implemented `IScope`
102+
* interface. New operations against the underlying `IScope` instance must be added here. Operations
103+
* not directly delegable to `IScope` should ***not*** be added.
104+
*/
105+
internal interface INavScope {
106+
107+
var screen: String?
108+
109+
/** A copy of the scope's app context, if any. */
110+
val app: App?
111+
112+
/**
113+
* The transaction bound to the scope, if any, as far as starting a nav transaction is concerned:
114+
* see [NavScope.transaction].
115+
*/
116+
val transaction: ITransaction?
117+
118+
/** Binds [transaction] to the scope if no transaction is bound to it. */
119+
fun bindTransaction(transaction: ITransaction)
120+
121+
/**
122+
* Unbinds [transaction] from the scope if it's the bound transaction, and ensures it won't be
123+
* bound to the scope again.
124+
*/
125+
fun unbindTransaction(transaction: ITransaction)
126+
127+
/** Unbinds the scope's transaction if it has finished. */
128+
fun clearFinishedTransaction()
129+
130+
/**
131+
* Sets or, given `null`, clears the value stored under [NAVIGATION_CONTEXT_KEY].
132+
*
133+
* Implementations should clear via [IScope.setContexts] rather than [IScope.removeContexts], as
134+
* the latter doesn't notify scope observers and therefore doesn't persist the removal to disk.
135+
*/
136+
fun setNavigationContext(value: Any?)
137+
138+
/** Replaces the scope's propagation context with a new one. */
139+
fun rotatePropagationContext()
140+
}

0 commit comments

Comments
 (0)