Skip to content

Commit 728ee17

Browse files
committed
Changes from test app
1 parent b926b58 commit 728ee17

3 files changed

Lines changed: 52 additions & 4 deletions

File tree

backstack/src/commonMain/kotlin/com/slack/circuit/backstack/BackStack.kt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ public interface BackStack<R : Record> : Iterable<R> {
3535
/** The top-most record in the [BackStack], or `null` if the [BackStack] is empty. */
3636
public val topRecord: R?
3737

38+
/** The bottom-most record in the [BackStack], or `null` if the [BackStack] is empty. */
39+
public val rootRecord: R?
40+
3841
/**
3942
* Push a new [Record] onto the back stack. The new record will become the top of the stack.
4043
*
@@ -103,6 +106,20 @@ public interface BackStack<R : Record> : Iterable<R> {
103106
*/
104107
public fun containsRecord(record: R, includeSaved: Boolean): Boolean
105108

109+
/**
110+
* Whether a record with the given [key] is reachable within the back stack or saved state.
111+
* Reachable means that it is either currently in the visible back stack or if we popped `depth`
112+
* times, it would be found.
113+
*
114+
* @param key The record's key to look for.
115+
* @param depth How many records to consider popping from the top of the stack before considering
116+
* the key unreachable. A depth of zero means only check the current visible stack. A depth of 1
117+
* means check the current visible stack plus one record popped off the top, and so on.
118+
* @param includeSaved Whether to also check if the record is contained by any saved back stack
119+
* state. See [saveState].
120+
*/
121+
public fun isRecordReachable(key: String, depth: Int, includeSaved: Boolean): Boolean
122+
106123
@Stable
107124
public interface Record {
108125
/**

backstack/src/commonMain/kotlin/com/slack/circuit/backstack/SaveableBackStack.kt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import androidx.compose.runtime.saveable.rememberSaveable
2424
import androidx.compose.runtime.snapshots.Snapshot
2525
import com.slack.circuit.runtime.screen.PopResult
2626
import com.slack.circuit.runtime.screen.Screen
27+
import kotlin.math.min
2728
import kotlin.uuid.ExperimentalUuidApi
2829
import kotlin.uuid.Uuid
2930
import kotlinx.coroutines.CompletableDeferred
@@ -92,6 +93,9 @@ internal constructor(
9293
public override val topRecord: Record?
9394
get() = entryList.firstOrNull()
9495

96+
override val rootRecord: Record?
97+
get() = entryList.lastOrNull()
98+
9599
public override fun push(screen: Screen, resultKey: String?): Boolean {
96100
return push(screen, emptyMap(), resultKey)
97101
}
@@ -154,6 +158,24 @@ internal constructor(
154158
return false
155159
}
156160

161+
override fun isRecordReachable(key: String, depth: Int, includeSaved: Boolean): Boolean {
162+
if (depth < 0) return false
163+
// Check in the current entry list
164+
for (i in 0 until min(depth, entryList.size)) {
165+
if (entryList[i].key == key) return true
166+
}
167+
// If includeSaved, check saved backstack states too
168+
if (includeSaved && stateStore.isNotEmpty()) {
169+
val storedValues = stateStore.values
170+
for ((i, stored) in storedValues.withIndex()) {
171+
if (i >= depth) break
172+
// stored can mutate, so safely get the record.
173+
if (stored.getOrNull(i)?.key == key) return true
174+
}
175+
}
176+
return false
177+
}
178+
157179
public data class Record(
158180
override val screen: Screen,
159181
val args: Map<String, Any?> = emptyMap(),

circuit-foundation/src/commonMain/kotlin/com/slack/circuit/foundation/NavigableCircuitContent.kt

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import androidx.compose.runtime.getValue
3232
import androidx.compose.runtime.movableContentOf
3333
import androidx.compose.runtime.mutableStateOf
3434
import androidx.compose.runtime.remember
35+
import androidx.compose.runtime.rememberUpdatedState
3536
import androidx.compose.runtime.saveable.SaveableStateHolder
3637
import androidx.compose.runtime.saveable.rememberSaveableStateHolder
3738
import androidx.compose.runtime.setValue
@@ -193,14 +194,22 @@ private fun <R : Record> buildCircuitContentProviders(
193194
val recordKeys by
194195
remember { mutableStateOf(persistentSetOf<String>()) }
195196
.apply { value = backStack.map { it.key }.toPersistentSet() }
197+
val latestBackStack by rememberUpdatedState(backStack)
196198
DisposableEffect(recordKeys) {
197199
// Delay cleanup until the next backstack change.
198-
val contentNotInBackStack = previousContentProviders.keys.toSet() - recordKeys
200+
val contentNotInBackStack =
201+
previousContentProviders.keys.filterNot {
202+
latestBackStack.isRecordReachable(key = it, depth = 1, includeSaved = true) ||
203+
it in activeRecordKeys
204+
}
199205
onDispose {
200206
// Only remove the keys that are no longer in the backstack or composition.
201-
(contentNotInBackStack - recordKeys - activeRecordKeys).forEach {
202-
previousContentProviders.remove(it)
203-
}
207+
contentNotInBackStack
208+
.filterNot {
209+
latestBackStack.isRecordReachable(key = it, depth = 1, includeSaved = true) ||
210+
it in activeRecordKeys
211+
}
212+
.forEach { previousContentProviders.remove(it) }
204213
}
205214
}
206215
return backStack

0 commit comments

Comments
 (0)