这是indexloc提供的服务,不要输入任何密码
Skip to content

Nav3 updates #33

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ androidxTestExt = "1.2.1"
androidxTestRunner = "1.6.2"
androidxTestRules = "1.6.1"
dokka = "1.8.20"
jetbrainsCompose = "1.8.0-rc01"
jetbrainsLifecycle = "2.9.0-alpha07"
jetbrainsCompose = "1.8.0"
jetbrainsLifecycle = "2.9.0-beta01"
jetbrainsMaterial3Adaptive = "1.0.1"
junit4 = "4.13.2"
kotlin = "2.1.20"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,176 +18,18 @@ package com.tunjid.treenav.compose.navigation3.decorators

import androidx.activity.compose.LocalActivity
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.Stable
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.lifecycle.HasDefaultViewModelProviderFactory
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.SAVED_STATE_REGISTRY_OWNER_KEY
import androidx.lifecycle.SavedStateViewModelFactory
import androidx.lifecycle.VIEW_MODEL_STORE_OWNER_KEY
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelStore
import androidx.lifecycle.ViewModelStoreOwner
import androidx.lifecycle.enableSavedStateHandles
import androidx.lifecycle.viewmodel.CreationExtras
import androidx.lifecycle.viewmodel.MutableCreationExtras
import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.savedstate.SavedStateRegistryOwner
import androidx.savedstate.compose.LocalSavedStateRegistryOwner
import com.tunjid.treenav.compose.navigation3.NavEntry
import com.tunjid.treenav.compose.navigation3.NavEntryDecorator

/**
* Provides the content of a [NavEntry] with a [ViewModelStoreOwner] and provides that
* [ViewModelStoreOwner] as a [LocalViewModelStoreOwner] so that it is available within the content.
*
* This requires that usage of the [SavedStateNavEntryDecorator] to ensure that the [NavEntry]
* scoped [ViewModel]s can properly provide access to [SavedStateHandle]s
*/
internal object AndroidViewModelStoreNavEntryDecorator : NavEntryDecorator {

@Composable
override fun DecorateBackStack(backStack: List<Any>, content: @Composable () -> Unit) {
val entryViewModelStoreProvider = viewModel { EntryViewModel() }
entryViewModelStoreProvider.ownerInBackStack.clear()
entryViewModelStoreProvider.ownerInBackStack.addAll(backStack)
val localInfo = remember { ViewModelStoreNavLocalInfo() }
DisposableEffect(key1 = backStack) { onDispose { localInfo.refCount.clear() } }

val activity = LocalActivity.current
backStack.forEachIndexed { index, key ->
// We update here as part of composition to ensure the value is available to
// DecorateEntry
localInfo.refCount.getOrPut(key) { LinkedHashSet<Int>() }.add(getIdForKey(key, index))
DisposableEffect(key1 = key) {
localInfo.refCount
.getOrPut(key) { LinkedHashSet<Int>() }
.add(getIdForKey(key, index))
onDispose {
// If the backStack count is less than the refCount for the key, remove the
// state since that means we removed a key from the backstack, and set the
// refCount to the backstack count.
val backstackCount = backStack.count { it == key }
val lastKeyCount = localInfo.refCount[key]?.size ?: 0
if (backstackCount < lastKeyCount) {
// The set of the ids associated with this key
@Suppress("PrimitiveInCollection") // The order of the element matters
val idsSet = localInfo.refCount[key]!!
val id = idsSet.last()
idsSet.remove(id)
if (!localInfo.idsInComposition.contains(id)) {
if (activity?.isChangingConfigurations != true) {
entryViewModelStoreProvider
.removeViewModelStoreOwnerForKey(id)
?.clear()
}
}
}

// If the refCount is 0, remove the key from the refCount.
if (localInfo.refCount[key]?.isEmpty() == true) {
localInfo.refCount.remove(key)
}
}
}
}

CompositionLocalProvider(LocalViewModelStoreNavLocalInfo provides localInfo) {
content.invoke()
}
}

@Composable
override fun <T : Any> DecorateEntry(entry: NavEntry<T>) {
val key = entry.key
val entryViewModelStoreProvider = viewModel { EntryViewModel() }

val activity = LocalActivity.current
val localInfo = LocalViewModelStoreNavLocalInfo.current
// Tracks whether the key is changed
var keyChanged = false
var id: Int =
rememberSaveable(key) {
keyChanged = true
localInfo.refCount[key]!!.last()
}
id =
rememberSaveable(localInfo.refCount[key]?.size) {
// if the key changed, use the current id
// If the key was not changed, and the current id is not in composition or on the
// back stack then update the id with the last item from the backstack with the
// associated key. This ensures that we can handle duplicates, both consecutive and
// non-consecutive
if (
!keyChanged &&
(!localInfo.idsInComposition.contains(id) ||
localInfo.refCount[key]?.contains(id) == true)
) {
localInfo.refCount[key]!!.last()
} else {
id
}
}
keyChanged = false

val viewModelStore = entryViewModelStoreProvider.viewModelStoreForKey(id)

DisposableEffect(key1 = key) {
localInfo.idsInComposition.add(id)
onDispose {
if (localInfo.idsInComposition.remove(id) && !localInfo.refCount.contains(key)) {
if (activity?.isChangingConfigurations != true) {
entryViewModelStoreProvider.removeViewModelStoreOwnerForKey(id)?.clear()
}
// If the refCount is 0, remove the key from the refCount.
if (localInfo.refCount[key]?.isEmpty() == true) {
localInfo.refCount.remove(key)
}
}
}
}

val savedStateRegistryOwner = LocalSavedStateRegistryOwner.current
val childViewModelOwner = remember {
object :
ViewModelStoreOwner,
SavedStateRegistryOwner by savedStateRegistryOwner,
HasDefaultViewModelProviderFactory {
override val viewModelStore: ViewModelStore
get() = viewModelStore

override val defaultViewModelProviderFactory: ViewModelProvider.Factory
get() = SavedStateViewModelFactory(null, savedStateRegistryOwner)

override val defaultViewModelCreationExtras: CreationExtras
get() =
MutableCreationExtras().also {
it[SAVED_STATE_REGISTRY_OWNER_KEY] = savedStateRegistryOwner
it[VIEW_MODEL_STORE_OWNER_KEY] = this
}

init {
require(this.lifecycle.currentState == Lifecycle.State.INITIALIZED) {
"The Lifecycle state is already beyond INITIALIZED. The " +
"ViewModelStoreNavEntryDecorator requires adding the " +
"SavedStateNavEntryDecorator to ensure support for " +
"SavedStateHandles."
}
enableSavedStateHandles()
}
}
}
CompositionLocalProvider(LocalViewModelStoreOwner provides childViewModelOwner) {
entry.content.invoke(key)
}
}
@Composable
internal actual fun shouldRemoveViewModelStoreCallback(): () -> Boolean {
val activity = LocalActivity.current
return { activity?.isChangingConfigurations != true }
}

@Stable
internal actual val ViewModelStoreNavEntryDecorator: NavEntryDecorator
get() = AndroidViewModelStoreNavEntryDecorator
internal actual fun SavedStateViewModelFactory(
savedStateRegistryOwner: SavedStateRegistryOwner
): SavedStateViewModelFactory = SavedStateViewModelFactory(
null,
savedStateRegistryOwner
)
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,10 @@ import androidx.compose.runtime.staticCompositionLocalOf
import com.tunjid.treenav.Node
import com.tunjid.treenav.compose.navigation3.DecoratedNavEntryProvider
import com.tunjid.treenav.compose.navigation3.NavEntry
import com.tunjid.treenav.compose.navigation3.decorators.MovableContentNavEntryDecorator
import com.tunjid.treenav.compose.navigation3.decorators.SaveableStateNavEntryDecorator
import com.tunjid.treenav.compose.navigation3.decorators.SavedStateNavEntryDecorator
import com.tunjid.treenav.compose.navigation3.decorators.TransitionAwareLifecycleNavEntryDecorator
import com.tunjid.treenav.compose.navigation3.decorators.ViewModelStoreNavEntryDecorator
import com.tunjid.treenav.compose.navigation3.decorators.rememberMovableContentNavEntryDecorator
import com.tunjid.treenav.compose.navigation3.decorators.rememberSavedStateNavEntryDecorator
import com.tunjid.treenav.compose.navigation3.decorators.rememberViewModelStoreNavEntryDecorator
import com.tunjid.treenav.compose.navigation3.decorators.transitionAwareLifecycleNavEntryDecorator

@Composable
internal fun <Destination : Node, NavigationState : Node, Pane> DecoratedNavEntryMultiPaneDisplayScope(
Expand All @@ -61,10 +60,6 @@ internal fun <Destination : Node, NavigationState : Node, Pane> DecoratedNavEntr
state.destinationTransform(navigationState)
)

val transitionAwareLifecycleNavEntryDecorator = remember {
TransitionAwareLifecycleNavEntryDecorator()
}

DecoratedNavEntryProvider(
backStack = backStack,
entryProvider = { node ->
Expand All @@ -78,11 +73,16 @@ internal fun <Destination : Node, NavigationState : Node, Pane> DecoratedNavEntr
)
},
entryDecorators = listOf(
MovableContentNavEntryDecorator,
SaveableStateNavEntryDecorator,
SavedStateNavEntryDecorator,
transitionAwareLifecycleNavEntryDecorator,
ViewModelStoreNavEntryDecorator,
rememberMovableContentNavEntryDecorator(),
rememberSavedStateNavEntryDecorator(),
transitionAwareLifecycleNavEntryDecorator(
backStack = backStack,
isSettled = {
val scope = LocalPaneScope.current
scope.transition.currentState == scope.transition.targetState
}
),
rememberViewModelStoreNavEntryDecorator(),
),
content = { entries ->
val updatedEntries by rememberUpdatedState(entries)
Expand Down
Loading