Merge changes from topic "snapshots_on" into androidx-main

* changes:
  Turn on snapshots for nav3 libraries
  Add new ViewModelStoreNavContentWrapper
diff --git a/lifecycle/lifecycle-viewmodel-navigation3/androidx-lifecycle-viewmodel-navigation3-documentation.md b/lifecycle/lifecycle-viewmodel-navigation3/androidx-lifecycle-viewmodel-navigation3-documentation.md
new file mode 100644
index 0000000..d5cea18
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel-navigation3/androidx-lifecycle-viewmodel-navigation3-documentation.md
@@ -0,0 +1,14 @@
+# EXPERIMENTAL
+
+## This is an **EXPERIMENTAL** library, use at your own risk
+
+This library is under heavy development and is in no way a final solution. This should only be used
+as a prototype or playground.
+
+# Module root
+
+androidx.lifecycle lifecycle-viewmodel-navigation3
+
+# Package androidx.lifecycle.viewmodel.navigation3
+
+Library that provides ViewModel extensions for the Navigation 3 library.
diff --git a/lifecycle/lifecycle-viewmodel-navigation3/api/current.txt b/lifecycle/lifecycle-viewmodel-navigation3/api/current.txt
new file mode 100644
index 0000000..dde49dd
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel-navigation3/api/current.txt
@@ -0,0 +1,10 @@
+// Signature format: 4.0
+package androidx.lifecycle.viewmodel.navigation3 {
+
+  public final class ViewModelStoreNavContentWrapper implements androidx.navigation3.NavContentWrapper {
+    method @androidx.compose.runtime.Composable public void WrapContent(androidx.navigation3.Record record);
+    field public static final androidx.lifecycle.viewmodel.navigation3.ViewModelStoreNavContentWrapper INSTANCE;
+  }
+
+}
+
diff --git a/lifecycle/lifecycle-viewmodel-navigation3/api/res-current.txt b/lifecycle/lifecycle-viewmodel-navigation3/api/res-current.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel-navigation3/api/res-current.txt
diff --git a/lifecycle/lifecycle-viewmodel-navigation3/api/restricted_current.txt b/lifecycle/lifecycle-viewmodel-navigation3/api/restricted_current.txt
new file mode 100644
index 0000000..dde49dd
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel-navigation3/api/restricted_current.txt
@@ -0,0 +1,10 @@
+// Signature format: 4.0
+package androidx.lifecycle.viewmodel.navigation3 {
+
+  public final class ViewModelStoreNavContentWrapper implements androidx.navigation3.NavContentWrapper {
+    method @androidx.compose.runtime.Composable public void WrapContent(androidx.navigation3.Record record);
+    field public static final androidx.lifecycle.viewmodel.navigation3.ViewModelStoreNavContentWrapper INSTANCE;
+  }
+
+}
+
diff --git a/lifecycle/lifecycle-viewmodel-navigation3/build.gradle b/lifecycle/lifecycle-viewmodel-navigation3/build.gradle
new file mode 100644
index 0000000..c270ddf
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel-navigation3/build.gradle
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * This file was created using the `create_project.py` script located in the
+ * `<AndroidX root>/development/project-creator` directory.
+ *
+ * Please use that script when creating a new project, rather than copying an existing project and
+ * modifying its settings.
+ */
+import androidx.build.LibraryType
+import androidx.build.Publish
+import androidx.build.PlatformIdentifier
+
+plugins {
+    id("AndroidXPlugin")
+    id("AndroidXComposePlugin")
+    id("com.android.library")
+}
+
+androidXMultiplatform {
+    android()
+
+    defaultPlatform(PlatformIdentifier.ANDROID)
+
+    sourceSets {
+        commonMain {
+            dependencies {
+                implementation(libs.kotlinStdlib)
+                implementation("androidx.compose.runtime:runtime:1.7.5")
+                implementation("androidx.compose.runtime:runtime-saveable:1.7.5")
+                implementation("androidx.lifecycle:lifecycle-viewmodel:2.8.7")
+                implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.7")
+            }
+        }
+
+        commonTest {
+            dependencies {
+                implementation(libs.kotlinTest)
+                implementation(project(":kruth:kruth"))
+                implementation(project(":compose:runtime:runtime-test-utils"))
+            }
+        }
+
+        jvmMain {
+            dependsOn(commonMain)
+            dependencies {
+            }
+        }
+
+        androidMain {
+            dependsOn(jvmMain)
+            dependencies {
+                implementation("androidx.compose.ui:ui:1.7.5")
+                implementation project(":navigation3:navigation3")
+            }
+        }
+
+        jvmTest {
+            dependsOn(commonTest)
+            dependencies {
+            }
+        }
+
+        androidInstrumentedTest {
+            dependsOn(jvmTest)
+            dependencies {
+                implementation(libs.testRules)
+                implementation(libs.testRunner)
+                implementation(libs.junit)
+                implementation(libs.testExtJunitKtx)
+                implementation(libs.truth)
+                implementation(project(":compose:test-utils"))
+                implementation("androidx.compose.ui:ui-test:1.7.5")
+                implementation("androidx.compose.ui:ui-test-junit4:1.7.5")
+            }
+        }
+    }
+}
+
+android {
+    compileSdk 35
+    namespace "androidx.lifecycle.viewmodel.navigation3"
+}
+
+androidx {
+    name = "Androidx Lifecycle Navigation3 ViewModel"
+    publish = Publish.SNAPSHOT_ONLY
+    type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
+    inceptionYear = "2024"
+    description = "Provides the ViewModel wrapper for nav3."
+    doNotDocumentReason = "Not published to maven"
+}
diff --git a/lifecycle/lifecycle-viewmodel-navigation3/src/androidInstrumentedTest/kotlin/androidx/lifecycle/viewmodel/navigation3/ViewModelStoreNavContentWrapperTest.kt b/lifecycle/lifecycle-viewmodel-navigation3/src/androidInstrumentedTest/kotlin/androidx/lifecycle/viewmodel/navigation3/ViewModelStoreNavContentWrapperTest.kt
new file mode 100644
index 0000000..7d38a31
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel-navigation3/src/androidInstrumentedTest/kotlin/androidx/lifecycle/viewmodel/navigation3/ViewModelStoreNavContentWrapperTest.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.lifecycle.viewmodel.navigation3
+
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.kruth.assertWithMessage
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewmodel.compose.viewModel
+import androidx.navigation3.Record
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import kotlin.test.Test
+import org.junit.Rule
+import org.junit.runner.RunWith
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+class ViewModelStoreNavContentWrapperTest {
+    @get:Rule val composeTestRule = createComposeRule()
+
+    @Test
+    fun testViewModelProvided() {
+        val wrapper = ViewModelStoreNavContentWrapper
+        lateinit var viewModel1: MyViewModel
+        lateinit var viewModel2: MyViewModel
+        val record1Arg = "record1 Arg"
+        val record2Arg = "record2 Arg"
+        val record1 =
+            Record("key1") {
+                viewModel1 = viewModel<MyViewModel>()
+                viewModel1.myArg = record1Arg
+            }
+        val record2 =
+            Record("key2") {
+                viewModel2 = viewModel<MyViewModel>()
+                viewModel2.myArg = record2Arg
+            }
+        composeTestRule.setContent {
+            wrapper.WrapContent(record1)
+            wrapper.WrapContent(record2)
+        }
+
+        composeTestRule.runOnIdle {
+            assertWithMessage("Incorrect arg for record 1")
+                .that(viewModel1.myArg)
+                .isEqualTo(record1Arg)
+            assertWithMessage("Incorrect arg for record 2")
+                .that(viewModel2.myArg)
+                .isEqualTo(record2Arg)
+        }
+    }
+}
+
+class MyViewModel : ViewModel() {
+    var myArg = "default"
+}
diff --git a/lifecycle/lifecycle-viewmodel-navigation3/src/androidMain/kotlin/androidx/lifecycle/viewmodel/navigation3/ViewModelStoreNavContentWrapper.android.kt b/lifecycle/lifecycle-viewmodel-navigation3/src/androidMain/kotlin/androidx/lifecycle/viewmodel/navigation3/ViewModelStoreNavContentWrapper.android.kt
new file mode 100644
index 0000000..e319249
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel-navigation3/src/androidMain/kotlin/androidx/lifecycle/viewmodel/navigation3/ViewModelStoreNavContentWrapper.android.kt
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.lifecycle.viewmodel.navigation3
+
+import android.app.Activity
+import android.content.Context
+import android.content.ContextWrapper
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.RememberObserver
+import androidx.compose.runtime.remember
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalSavedStateRegistryOwner
+import androidx.lifecycle.HasDefaultViewModelProviderFactory
+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.viewmodel.CreationExtras
+import androidx.lifecycle.viewmodel.MutableCreationExtras
+import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
+import androidx.lifecycle.viewmodel.compose.viewModel
+import androidx.navigation3.NavContentWrapper
+import androidx.navigation3.Record
+
+/**
+ * Provides the content of a [Record] with a [ViewModelStoreOwner] and provides that
+ * [ViewModelStoreOwner] as a [LocalViewModelStoreOwner] so that it is available within the content.
+ */
+public object ViewModelStoreNavContentWrapper : NavContentWrapper {
+
+    @Composable
+    override fun WrapContent(record: Record) {
+        val key = record.key
+        val recordViewModelStoreProvider = viewModel { RecordViewModel() }
+        val viewModelStore = recordViewModelStoreProvider.viewModelStoreForKey(key)
+        // This ensures we always keep viewModels on config changes.
+        val activity = LocalContext.current.findActivity()
+        remember(key, viewModelStore) {
+            object : RememberObserver {
+                override fun onAbandoned() {
+                    disposeIfNotChangingConfiguration()
+                }
+
+                override fun onForgotten() {
+                    disposeIfNotChangingConfiguration()
+                }
+
+                override fun onRemembered() {}
+
+                fun disposeIfNotChangingConfiguration() {
+                    if (activity?.isChangingConfigurations != true) {
+                        recordViewModelStoreProvider.removeViewModelStoreOwnerForKey(key)?.clear()
+                    }
+                }
+            }
+        }
+
+        val savedStateRegistryOwner = LocalSavedStateRegistryOwner.current
+        CompositionLocalProvider(
+            LocalViewModelStoreOwner provides
+                object : ViewModelStoreOwner, 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
+                            }
+                }
+        ) {
+            record.content.invoke(key)
+        }
+    }
+}
+
+private class RecordViewModel : ViewModel() {
+    private val owners = mutableMapOf<Any, ViewModelStore>()
+
+    fun viewModelStoreForKey(key: Any): ViewModelStore = owners.getOrPut(key) { ViewModelStore() }
+
+    fun removeViewModelStoreOwnerForKey(key: Any): ViewModelStore? = owners.remove(key)
+
+    override fun onCleared() {
+        owners.forEach { (_, store) -> store.clear() }
+    }
+}
+
+private fun Context.findActivity(): Activity? {
+    var context = this
+    while (context is ContextWrapper) {
+        if (context is Activity) return context
+        context = context.baseContext
+    }
+    return null
+}
diff --git a/navigation3/navigation3/api/current.txt b/navigation3/navigation3/api/current.txt
index b0e1510..17c13b7 100644
--- a/navigation3/navigation3/api/current.txt
+++ b/navigation3/navigation3/api/current.txt
@@ -27,6 +27,12 @@
 
   public final class Record {
     ctor public Record(Object key, optional java.util.Map<java.lang.String,?> featureMap, kotlin.jvm.functions.Function1<java.lang.Object,kotlin.Unit> content);
+    method public kotlin.jvm.functions.Function1<java.lang.Object,kotlin.Unit> getContent();
+    method public java.util.Map<java.lang.String,java.lang.Object> getFeatureMap();
+    method public Object getKey();
+    property public final kotlin.jvm.functions.Function1<java.lang.Object,kotlin.Unit> content;
+    property public final java.util.Map<java.lang.String,java.lang.Object> featureMap;
+    property public final Object key;
   }
 
   public final class SaveableStateNavContentWrapper implements androidx.navigation3.NavContentWrapper {
diff --git a/navigation3/navigation3/api/restricted_current.txt b/navigation3/navigation3/api/restricted_current.txt
index b0e1510..17c13b7 100644
--- a/navigation3/navigation3/api/restricted_current.txt
+++ b/navigation3/navigation3/api/restricted_current.txt
@@ -27,6 +27,12 @@
 
   public final class Record {
     ctor public Record(Object key, optional java.util.Map<java.lang.String,?> featureMap, kotlin.jvm.functions.Function1<java.lang.Object,kotlin.Unit> content);
+    method public kotlin.jvm.functions.Function1<java.lang.Object,kotlin.Unit> getContent();
+    method public java.util.Map<java.lang.String,java.lang.Object> getFeatureMap();
+    method public Object getKey();
+    property public final kotlin.jvm.functions.Function1<java.lang.Object,kotlin.Unit> content;
+    property public final java.util.Map<java.lang.String,java.lang.Object> featureMap;
+    property public final Object key;
   }
 
   public final class SaveableStateNavContentWrapper implements androidx.navigation3.NavContentWrapper {
diff --git a/navigation3/navigation3/build.gradle b/navigation3/navigation3/build.gradle
index 361be2d..f61edaa 100644
--- a/navigation3/navigation3/build.gradle
+++ b/navigation3/navigation3/build.gradle
@@ -111,7 +111,7 @@
 
 androidx {
     name = "Androidx Navigation 3"
-    publish = Publish.NONE
+    publish = Publish.SNAPSHOT_ONLY
     type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
     inceptionYear = "2024"
     description = "Provides the building blocks for a Compose first Navigation solution that " +
diff --git a/navigation3/navigation3/samples/build.gradle b/navigation3/navigation3/samples/build.gradle
index 0576226..b2b8be8 100644
--- a/navigation3/navigation3/samples/build.gradle
+++ b/navigation3/navigation3/samples/build.gradle
@@ -43,7 +43,10 @@
     implementation("androidx.compose.material3:material3:1.3.1")
     implementation("androidx.compose.runtime:runtime:1.7.5")
     implementation("androidx.compose.ui:ui:1.7.5")
+    implementation("androidx.lifecycle:lifecycle-viewmodel:2.8.7")
+    implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.7")
     implementation(libs.kotlinSerializationCore)
+    implementation project(":lifecycle:lifecycle-viewmodel-navigation3")
     implementation project(":navigation3:navigation3")
 
 }
diff --git a/navigation3/navigation3/samples/src/main/kotlin/androidx/navigation3/samples/NavDisplaySamples.kt b/navigation3/navigation3/samples/src/main/kotlin/androidx/navigation3/samples/NavDisplaySamples.kt
index 5158efa..f7adfa2 100644
--- a/navigation3/navigation3/samples/src/main/kotlin/androidx/navigation3/samples/NavDisplaySamples.kt
+++ b/navigation3/navigation3/samples/src/main/kotlin/androidx/navigation3/samples/NavDisplaySamples.kt
@@ -19,6 +19,9 @@
 import androidx.annotation.Sampled
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewmodel.compose.viewModel
+import androidx.lifecycle.viewmodel.navigation3.ViewModelStoreNavContentWrapper
 import androidx.navigation3.NavDisplay
 import androidx.navigation3.Record
 import androidx.navigation3.SavedStateNavContentWrapper
@@ -28,7 +31,10 @@
 @Composable
 fun BasicNav() {
     val backStack = rememberMutableStateListOf(Profile)
-    val manager = rememberNavWrapperManager(listOf(SavedStateNavContentWrapper))
+    val manager =
+        rememberNavWrapperManager(
+            listOf(SavedStateNavContentWrapper, ViewModelStoreNavContentWrapper)
+        )
     NavDisplay(
         backstack = backStack,
         wrapperManager = manager,
@@ -36,7 +42,10 @@
     ) { key ->
         when (key) {
             Profile -> {
-                Record(Profile) { Profile({ backStack.add(it) }) { backStack.removeLast() } }
+                Record(Profile) {
+                    val viewModel = viewModel<ProfileViewModel>()
+                    Profile(viewModel, { backStack.add(it) }) { backStack.removeLast() }
+                }
             }
             Scrollable -> {
                 Record(Scrollable) { Scrollable({ backStack.add(it) }) { backStack.removeLast() } }
@@ -58,3 +67,7 @@
         }
     }
 }
+
+class ProfileViewModel : ViewModel() {
+    val name = "no user"
+}
diff --git a/navigation3/navigation3/samples/src/main/kotlin/androidx/navigation3/samples/NavigationSamples.kt b/navigation3/navigation3/samples/src/main/kotlin/androidx/navigation3/samples/NavigationSamples.kt
index 55ea9eb1..f688908 100644
--- a/navigation3/navigation3/samples/src/main/kotlin/androidx/navigation3/samples/NavigationSamples.kt
+++ b/navigation3/navigation3/samples/src/main/kotlin/androidx/navigation3/samples/NavigationSamples.kt
@@ -65,9 +65,9 @@
 }
 
 @Composable
-fun Profile(navigateTo: (Any) -> Unit, onBack: () -> Unit) {
+fun Profile(viewModel: ProfileViewModel, navigateTo: (Any) -> Unit, onBack: () -> Unit) {
     Column(Modifier.fillMaxSize().then(Modifier.padding(8.dp))) {
-        Text(text = stringResource(Profile.resourceId))
+        Text(text = "${viewModel.name} ${stringResource(Profile.resourceId)}")
         NavigateButton(stringResource(Dashboard.resourceId)) { navigateTo(Dashboard()) }
         Divider(color = Color.Black)
         NavigateButton(stringResource(Scrollable.resourceId)) { navigateTo(Scrollable) }
diff --git a/navigation3/navigation3/src/commonMain/kotlin/androidx/navigation3/Record.kt b/navigation3/navigation3/src/commonMain/kotlin/androidx/navigation3/Record.kt
index 5c378c6..607e6ba 100644
--- a/navigation3/navigation3/src/commonMain/kotlin/androidx/navigation3/Record.kt
+++ b/navigation3/navigation3/src/commonMain/kotlin/androidx/navigation3/Record.kt
@@ -27,7 +27,7 @@
  * @param content content for this record to be displayed when this record is active
  */
 public class Record(
-    internal val key: Any,
-    internal val featureMap: Map<String, Any> = emptyMap(),
-    internal val content: @Composable (Any) -> Unit,
+    public val key: Any,
+    public val featureMap: Map<String, Any> = emptyMap(),
+    public val content: @Composable (Any) -> Unit,
 )
diff --git a/settings.gradle b/settings.gradle
index 50266ac..89583eb 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -801,6 +801,7 @@
 includeProject(":lifecycle:lifecycle-viewmodel-savedstate", [BuildType.MAIN, BuildType.FLAN, BuildType.COMPOSE, BuildType.KMP])
 includeProject(":lifecycle:lifecycle-viewmodel-savedstate-samples", [BuildType.MAIN, BuildType.FLAN, BuildType.COMPOSE, BuildType.KMP])
 includeProject(":lifecycle:lifecycle-viewmodel-testing", [BuildType.MAIN, BuildType.FLAN, BuildType.COMPOSE, BuildType.INFRAROGUE, BuildType.KMP])
+includeProject(":lifecycle:lifecycle-viewmodel-navigation3", [BuildType.COMPOSE])
 includeProject(":lint:lint-gradle", [BuildType.MAIN])
 includeProject(":lint-checks")
 includeProject(":lint-checks:integration-tests")