parent
386d1a0a16
commit
f6824858c4
@ -1,62 +0,0 @@
|
||||
/*
|
||||
* Copyright 2025 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
|
||||
*
|
||||
* https://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 com.google.samples.apps.nowinandroid.di
|
||||
|
||||
// TODO: Rename to `NiaNavigationStateProvider`
|
||||
// Does this even need to be injected? Can't we just instantiate it directly using `rememberNavigationState`?
|
||||
/*
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object NavigationStateProvider {
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideNavigationState(): NiaNavigationState =
|
||||
NiaNavigationState(
|
||||
//startKey = TopLevelDestination.FOR_YOU.key,
|
||||
startKey = object : NiaNavKey {
|
||||
override val isTopLevel: Boolean
|
||||
get() = true
|
||||
}
|
||||
)
|
||||
|
||||
// TODO: Remove commented out code
|
||||
//
|
||||
// @Provides
|
||||
// @Singleton
|
||||
// fun provideNiaNavigator(
|
||||
// state: NiaNavigatorState
|
||||
// ): NiaNavigator =
|
||||
// NiaNavigator(state)
|
||||
|
||||
*/
|
||||
/**
|
||||
* Registers feature modules' polymorphic serializers to support
|
||||
* feature keys' save and restore by savedstate
|
||||
* in [com.google.samples.apps.nowinandroid.core.navigation.NiaBackStackViewModel].
|
||||
*//*
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSerializersModule(
|
||||
polymorphicModuleBuilders: Set<@JvmSuppressWildcards PolymorphicModuleBuilder<NiaNavKey>.() -> Unit>,
|
||||
): SerializersModule = SerializersModule {
|
||||
polymorphic(NiaNavKey::class) {
|
||||
polymorphicModuleBuilders.forEach { it() }
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
@ -1,135 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022 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
|
||||
*
|
||||
* https://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 com.google.samples.apps.nowinandroid.navigation
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.navigation3.runtime.NavKey
|
||||
import com.google.samples.apps.nowinandroid.R
|
||||
import com.google.samples.apps.nowinandroid.core.designsystem.icon.NiaIcons
|
||||
import com.google.samples.apps.nowinandroid.feature.bookmarks.api.navigation.BookmarksRoute
|
||||
import com.google.samples.apps.nowinandroid.feature.foryou.api.navigation.ForYouRoute
|
||||
import com.google.samples.apps.nowinandroid.feature.interests.api.navigation.InterestsRoute
|
||||
import kotlin.reflect.KClass
|
||||
import com.google.samples.apps.nowinandroid.feature.bookmarks.api.R as bookmarksR
|
||||
import com.google.samples.apps.nowinandroid.feature.foryou.api.R as forYouR
|
||||
import com.google.samples.apps.nowinandroid.feature.search.api.R as searchR
|
||||
|
||||
/**
|
||||
* Type for the top level destinations in the application. Contains metadata about the destination
|
||||
* that is used in the top app bar and common navigation UI.
|
||||
*
|
||||
* @param selectedIcon The icon to be displayed in the navigation UI when this destination is
|
||||
* selected.
|
||||
* @param unselectedIcon The icon to be displayed in the navigation UI when this destination is
|
||||
* not selected.
|
||||
* @param iconTextId Text that to be displayed in the navigation UI.
|
||||
* @param titleTextId Text that is displayed on the top app bar.
|
||||
* @param route The route to use when navigating to this destination.
|
||||
* @param baseRoute The highest ancestor of this destination. Defaults to [route], meaning that
|
||||
* there is a single destination in that section of the app (no nested destinations).
|
||||
*/
|
||||
/*
|
||||
enum class TopLevelDestination(
|
||||
val selectedIcon: ImageVector,
|
||||
val unselectedIcon: ImageVector,
|
||||
@StringRes val iconTextId: Int,
|
||||
@StringRes val titleTextId: Int,
|
||||
val route: KClass<*>,
|
||||
val key: NiaNavKey,
|
||||
) {
|
||||
FOR_YOU(
|
||||
selectedIcon = NiaIcons.Upcoming,
|
||||
unselectedIcon = NiaIcons.UpcomingBorder,
|
||||
iconTextId = forYouR.string.feature_foryou_api_title,
|
||||
titleTextId = R.string.app_name,
|
||||
route = ForYouRoute::class,
|
||||
key = ForYouRoute,
|
||||
),
|
||||
BOOKMARKS(
|
||||
selectedIcon = NiaIcons.Bookmarks,
|
||||
unselectedIcon = NiaIcons.BookmarksBorder,
|
||||
iconTextId = bookmarksR.string.feature_bookmarks_api_title,
|
||||
titleTextId = bookmarksR.string.feature_bookmarks_api_title,
|
||||
route = BookmarksRoute::class,
|
||||
key = BookmarksRoute,
|
||||
),
|
||||
INTERESTS(
|
||||
selectedIcon = NiaIcons.Grid3x3,
|
||||
unselectedIcon = NiaIcons.Grid3x3,
|
||||
iconTextId = searchR.string.feature_search_api_interests,
|
||||
titleTextId = searchR.string.feature_search_api_interests,
|
||||
route = InterestsRoute::class,
|
||||
key = InterestsRoute(null),
|
||||
),
|
||||
}
|
||||
|
||||
internal val TopLevelDestinations = TopLevelDestination.entries.associateBy { dest -> dest.key }
|
||||
*/
|
||||
|
||||
val FOR_YOU = NavBarItem(
|
||||
selectedIcon = NiaIcons.Upcoming,
|
||||
unselectedIcon = NiaIcons.UpcomingBorder,
|
||||
iconTextId = forYouR.string.feature_foryou_api_title,
|
||||
titleTextId = R.string.app_name,
|
||||
key = ForYouRoute,
|
||||
)
|
||||
|
||||
val BOOKMARKS = NavBarItem(
|
||||
selectedIcon = NiaIcons.Bookmarks,
|
||||
unselectedIcon = NiaIcons.BookmarksBorder,
|
||||
iconTextId = bookmarksR.string.feature_bookmarks_api_title,
|
||||
titleTextId = bookmarksR.string.feature_bookmarks_api_title,
|
||||
key = BookmarksRoute,
|
||||
)
|
||||
|
||||
val INTERESTS = NavBarItem(
|
||||
selectedIcon = NiaIcons.Grid3x3,
|
||||
unselectedIcon = NiaIcons.Grid3x3,
|
||||
iconTextId = searchR.string.feature_search_api_interests,
|
||||
titleTextId = searchR.string.feature_search_api_interests,
|
||||
key = InterestsRoute(null)
|
||||
)
|
||||
|
||||
|
||||
val TOP_LEVEL_ROUTES = mapOf<NavKey, NavBarItem>(
|
||||
ForYouRoute to FOR_YOU,
|
||||
BookmarksRoute to BOOKMARKS,
|
||||
InterestsRoute(null) to INTERESTS,
|
||||
)
|
||||
|
||||
|
||||
/**
|
||||
* Type for the top level navigation items in the application. Contains UI information about the
|
||||
* current route that is used in the top app bar and common navigation UI.
|
||||
*
|
||||
* @param selectedIcon The icon to be displayed in the navigation UI when this destination is
|
||||
* selected.
|
||||
* @param unselectedIcon The icon to be displayed in the navigation UI when this destination is
|
||||
* not selected.
|
||||
* @param iconTextId Text that to be displayed in the navigation UI.
|
||||
* @param titleTextId Text that is displayed on the top app bar.
|
||||
* @param key The navigation key to use when navigating to this destination.
|
||||
* there is a single destination in that section of the app (no nested destinations).
|
||||
*/
|
||||
data class NavBarItem(
|
||||
val selectedIcon: ImageVector,
|
||||
val unselectedIcon: ImageVector,
|
||||
@StringRes val iconTextId: Int,
|
||||
@StringRes val titleTextId: Int,
|
||||
val key: NavKey,
|
||||
)
|
||||
@ -1,41 +0,0 @@
|
||||
/*
|
||||
* Copyright 2025 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
|
||||
*
|
||||
* https://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 com.google.samples.apps.nowinandroid.navigation
|
||||
|
||||
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
|
||||
import androidx.compose.material3.adaptive.navigation3.rememberListDetailSceneStrategy
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.navigation3.runtime.EntryProviderScope
|
||||
import androidx.navigation3.ui.NavDisplay
|
||||
import com.google.samples.apps.nowinandroid.core.navigation.NiaNavKey
|
||||
import com.google.samples.apps.nowinandroid.core.navigation.NiaNavigator
|
||||
import com.google.samples.apps.nowinandroid.core.navigation.toEntries
|
||||
|
||||
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
|
||||
@Composable
|
||||
fun NiaNavDisplay(
|
||||
niaNavigator: NiaNavigator,
|
||||
entryProviderBuilders: Set<EntryProviderScope<NiaNavKey>.() -> Unit>,
|
||||
) {
|
||||
val listDetailStrategy = rememberListDetailSceneStrategy<NiaNavKey>()
|
||||
val entries = niaNavigator.niaNavigationState.toEntries(entryProviderBuilders)
|
||||
NavDisplay(
|
||||
entries = entries,
|
||||
sceneStrategy = listDetailStrategy,
|
||||
onBack = { niaNavigator.pop() },
|
||||
)
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2025 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
|
||||
*
|
||||
* https://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 com.google.samples.apps.nowinandroid.navigation
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
|
||||
/**
|
||||
* Type for the top level navigation items in the application. Contains UI information about the
|
||||
* current route that is used in the top app bar and common navigation UI.
|
||||
*
|
||||
* @param selectedIcon The icon to be displayed in the navigation UI when this destination is
|
||||
* selected.
|
||||
* @param unselectedIcon The icon to be displayed in the navigation UI when this destination is
|
||||
* not selected.
|
||||
* @param iconTextId Text that to be displayed in the navigation UI.
|
||||
* @param titleTextId Text that is displayed on the top app bar.
|
||||
*/
|
||||
data class TopLevelNavItem(
|
||||
val selectedIcon: ImageVector,
|
||||
val unselectedIcon: ImageVector,
|
||||
@StringRes val iconTextId: Int,
|
||||
@StringRes val titleTextId: Int,
|
||||
)
|
||||
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2022 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
|
||||
*
|
||||
* https://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 com.google.samples.apps.nowinandroid.navigation
|
||||
|
||||
import com.google.samples.apps.nowinandroid.R
|
||||
import com.google.samples.apps.nowinandroid.core.designsystem.icon.NiaIcons
|
||||
import com.google.samples.apps.nowinandroid.feature.bookmarks.api.navigation.BookmarksRoute
|
||||
import com.google.samples.apps.nowinandroid.feature.foryou.api.navigation.ForYouRoute
|
||||
import com.google.samples.apps.nowinandroid.feature.interests.api.navigation.InterestsRoute
|
||||
import com.google.samples.apps.nowinandroid.feature.bookmarks.api.R as bookmarksR
|
||||
import com.google.samples.apps.nowinandroid.feature.foryou.api.R as forYouR
|
||||
import com.google.samples.apps.nowinandroid.feature.search.api.R as searchR
|
||||
|
||||
val FOR_YOU = TopLevelNavItem(
|
||||
selectedIcon = NiaIcons.Upcoming,
|
||||
unselectedIcon = NiaIcons.UpcomingBorder,
|
||||
iconTextId = forYouR.string.feature_foryou_api_title,
|
||||
titleTextId = R.string.app_name,
|
||||
)
|
||||
|
||||
val BOOKMARKS = TopLevelNavItem(
|
||||
selectedIcon = NiaIcons.Bookmarks,
|
||||
unselectedIcon = NiaIcons.BookmarksBorder,
|
||||
iconTextId = bookmarksR.string.feature_bookmarks_api_title,
|
||||
titleTextId = bookmarksR.string.feature_bookmarks_api_title,
|
||||
)
|
||||
|
||||
val INTERESTS = TopLevelNavItem(
|
||||
selectedIcon = NiaIcons.Grid3x3,
|
||||
unselectedIcon = NiaIcons.Grid3x3,
|
||||
iconTextId = searchR.string.feature_search_api_interests,
|
||||
titleTextId = searchR.string.feature_search_api_interests,
|
||||
)
|
||||
|
||||
val TOP_LEVEL_ROUTES = mapOf(
|
||||
ForYouRoute to FOR_YOU,
|
||||
BookmarksRoute to BOOKMARKS,
|
||||
InterestsRoute(null) to INTERESTS,
|
||||
)
|
||||
@ -1,253 +0,0 @@
|
||||
/*
|
||||
* Copyright 2025 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
|
||||
*
|
||||
* https://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 com.google.samples.apps.nowinandroid.core.navigation
|
||||
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.test.junit4.createComposeRule
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.createSavedStateHandle
|
||||
import androidx.lifecycle.viewmodel.testing.ViewModelScenario
|
||||
import androidx.lifecycle.viewmodel.testing.viewModelScenario
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.modules.SerializersModule
|
||||
import kotlinx.serialization.modules.polymorphic
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class NiaBackStackViewModelTest {
|
||||
|
||||
@get:Rule val rule = createComposeRule()
|
||||
|
||||
private val serializersModules = SerializersModule {
|
||||
polymorphic(NiaNavKey::class) {
|
||||
subclass(TestStartKey::class, TestStartKey.serializer())
|
||||
subclass(TestTopLevelKeyFirst::class, TestTopLevelKeyFirst.serializer())
|
||||
subclass(TestTopLevelKeySecond::class, TestTopLevelKeySecond.serializer())
|
||||
subclass(TestKeyFirst::class, TestKeyFirst.serializer())
|
||||
subclass(TestKeySecond::class, TestKeySecond.serializer())
|
||||
}
|
||||
}
|
||||
|
||||
private fun createViewModel() = NiaBackStackViewModel(
|
||||
savedStateHandle = SavedStateHandle(),
|
||||
niaNavigationState = NiaNavigationState(TestStartKey),
|
||||
serializersModules = serializersModules,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun testStartKeySaved() {
|
||||
rule.setContent {
|
||||
val viewModel = createViewModel()
|
||||
assertThat(viewModel.backStackMap.size).isEqualTo(1)
|
||||
val entry = viewModel.backStackMap[TestStartKey]
|
||||
assertThat(entry).isNotNull()
|
||||
assertThat(entry).containsExactly(TestStartKey)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNonTopLevelKeySaved() {
|
||||
val viewModel = createViewModel()
|
||||
rule.setContent {
|
||||
val navigator = remember { NiaNavigator(viewModel.niaNavigationState) }
|
||||
navigator.navigate(TestKeyFirst)
|
||||
}
|
||||
assertThat(viewModel.backStackMap.size).isEqualTo(1)
|
||||
val entry = viewModel.backStackMap[TestStartKey]
|
||||
assertThat(entry).isNotNull()
|
||||
assertThat(entry).containsExactly(TestStartKey, TestKeyFirst).inOrder()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTopLevelKeySaved() {
|
||||
val viewModel = createViewModel()
|
||||
rule.setContent {
|
||||
val navigator = remember { NiaNavigator(viewModel.niaNavigationState) }
|
||||
|
||||
navigator.navigate(TestKeyFirst)
|
||||
navigator.navigate(TestTopLevelKeyFirst)
|
||||
}
|
||||
|
||||
assertThat(viewModel.backStackMap.size).isEqualTo(2)
|
||||
|
||||
val entry = viewModel.backStackMap[TestStartKey]
|
||||
assertThat(entry).isNotNull()
|
||||
assertThat(entry).containsExactly(TestStartKey, TestKeyFirst).inOrder()
|
||||
|
||||
val entry2 = viewModel.backStackMap[TestTopLevelKeyFirst]
|
||||
assertThat(entry2).isNotNull()
|
||||
assertThat(entry2).containsExactly(TestTopLevelKeyFirst)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMultiStacksSaved() {
|
||||
val viewModel = createViewModel()
|
||||
rule.setContent {
|
||||
val navigator = remember { NiaNavigator(viewModel.niaNavigationState) }
|
||||
navigator.navigate(TestKeyFirst)
|
||||
navigator.navigate(TestTopLevelKeyFirst)
|
||||
navigator.navigate(TestKeySecond)
|
||||
}
|
||||
|
||||
assertThat(viewModel.backStackMap.size).isEqualTo(2)
|
||||
|
||||
val entry = viewModel.backStackMap[TestStartKey]
|
||||
assertThat(entry).isNotNull()
|
||||
assertThat(entry).containsExactly(TestStartKey, TestKeyFirst).inOrder()
|
||||
|
||||
val entry2 = viewModel.backStackMap[TestTopLevelKeyFirst]
|
||||
assertThat(entry2).isNotNull()
|
||||
assertThat(entry2).containsExactly(TestTopLevelKeyFirst, TestKeySecond).inOrder()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPopSaved() {
|
||||
val viewModel = createViewModel()
|
||||
rule.setContent {
|
||||
val navigator = remember { NiaNavigator(viewModel.niaNavigationState) }
|
||||
|
||||
navigator.navigate(TestKeyFirst)
|
||||
|
||||
assertThat(viewModel.backStackMap.size).isEqualTo(1)
|
||||
val entry = viewModel.backStackMap[TestStartKey]
|
||||
assertThat(entry).isNotNull()
|
||||
assertThat(entry).containsExactly(TestStartKey, TestKeyFirst).inOrder()
|
||||
|
||||
navigator.pop()
|
||||
|
||||
assertThat(viewModel.backStackMap.size).isEqualTo(1)
|
||||
val entry2 = viewModel.backStackMap[TestStartKey]
|
||||
assertThat(entry2).isNotNull()
|
||||
assertThat(entry2).containsExactly(TestStartKey).inOrder()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRestore() {
|
||||
lateinit var scenario: ViewModelScenario<NiaBackStackViewModel>
|
||||
lateinit var navigator: NiaNavigator
|
||||
lateinit var navigatorState: NiaNavigationState
|
||||
rule.setContent {
|
||||
navigatorState = remember { NiaNavigationState(TestStartKey) }
|
||||
navigator = remember { NiaNavigator(navigatorState) }
|
||||
scenario = viewModelScenario {
|
||||
NiaBackStackViewModel(
|
||||
savedStateHandle = createSavedStateHandle(),
|
||||
niaNavigationState = navigatorState,
|
||||
serializersModules = serializersModules,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
rule.runOnIdle {
|
||||
navigator.navigate(TestKeyFirst)
|
||||
assertThat(navigatorState.currentBackStack).containsExactly(
|
||||
TestStartKey,
|
||||
TestKeyFirst,
|
||||
).inOrder()
|
||||
}
|
||||
|
||||
scenario.recreate()
|
||||
|
||||
rule.runOnIdle {
|
||||
assertThat(navigatorState.currentBackStack).containsExactly(
|
||||
TestStartKey,
|
||||
TestKeyFirst,
|
||||
).inOrder()
|
||||
}
|
||||
|
||||
scenario.close()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRestoreMultiStacks() {
|
||||
lateinit var scenario: ViewModelScenario<NiaBackStackViewModel>
|
||||
lateinit var navigator: NiaNavigator
|
||||
lateinit var navigatorState: NiaNavigationState
|
||||
rule.setContent {
|
||||
navigatorState = remember { NiaNavigationState(TestStartKey) }
|
||||
navigator = remember { NiaNavigator(navigatorState) }
|
||||
scenario = viewModelScenario {
|
||||
NiaBackStackViewModel(
|
||||
savedStateHandle = createSavedStateHandle(),
|
||||
niaNavigationState = navigatorState,
|
||||
serializersModules = serializersModules,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
rule.runOnIdle {
|
||||
navigator.navigate(TestKeyFirst)
|
||||
navigator.navigate(TestTopLevelKeyFirst)
|
||||
navigator.navigate(TestKeySecond)
|
||||
|
||||
assertThat(navigatorState.currentBackStack).containsExactly(
|
||||
TestStartKey,
|
||||
TestKeyFirst,
|
||||
TestTopLevelKeyFirst,
|
||||
TestKeySecond,
|
||||
).inOrder()
|
||||
}
|
||||
|
||||
scenario.recreate()
|
||||
|
||||
rule.runOnIdle {
|
||||
assertThat(navigatorState.currentBackStack).containsExactly(
|
||||
TestStartKey,
|
||||
TestKeyFirst,
|
||||
TestTopLevelKeyFirst,
|
||||
TestKeySecond,
|
||||
).inOrder()
|
||||
}
|
||||
|
||||
scenario.close()
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
private object TestStartKey : NiaNavKey {
|
||||
override val isTopLevel: Boolean
|
||||
get() = true
|
||||
}
|
||||
|
||||
@Serializable
|
||||
private object TestTopLevelKeyFirst : NiaNavKey {
|
||||
override val isTopLevel: Boolean
|
||||
get() = true
|
||||
}
|
||||
|
||||
@Serializable
|
||||
private object TestTopLevelKeySecond : NiaNavKey {
|
||||
override val isTopLevel: Boolean
|
||||
get() = true
|
||||
}
|
||||
|
||||
@Serializable
|
||||
private object TestKeyFirst : NiaNavKey {
|
||||
override val isTopLevel: Boolean
|
||||
get() = false
|
||||
}
|
||||
|
||||
@Serializable
|
||||
private object TestKeySecond : NiaNavKey {
|
||||
override val isTopLevel: Boolean
|
||||
get() = false
|
||||
}
|
||||
@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2025 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
|
||||
*
|
||||
* https://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 com.google.samples.apps.nowinandroid.core.navigation
|
||||
|
||||
import androidx.navigation3.runtime.NavKey
|
||||
|
||||
/**
|
||||
* Handles navigation events (forward and back) by updating the navigation state.
|
||||
*
|
||||
* @param state - The navigation state that will be updated in response to navigation events.
|
||||
*/
|
||||
class Navigator(val state: NavigationState) {
|
||||
|
||||
/**
|
||||
* Navigate to a navigation key
|
||||
*
|
||||
* @param key - the navigation key to navigate to.
|
||||
*/
|
||||
fun navigate(key: NavKey) {
|
||||
when (key) {
|
||||
state.currentTopLevelKey -> goUp()
|
||||
in state.topLevelKeys -> goToTopLevel(key)
|
||||
else -> goToKey(key)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Go back to the previous navigation key.
|
||||
*/
|
||||
fun goBack() {
|
||||
when (state.currentKey){
|
||||
state.startKey -> error("You cannot go back from the start route")
|
||||
state.currentTopLevelKey -> {
|
||||
// We're at the base of the current sub stack, go back to the previous top level
|
||||
// stack.
|
||||
state.topLevelStack.removeLastOrNull()
|
||||
}
|
||||
else -> state.currentSubStack.removeLastOrNull()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Go to a non top level key.
|
||||
*/
|
||||
private fun goToKey(key: NavKey) {
|
||||
state.currentSubStack.apply {
|
||||
// Remove it if it's already in the stack so it's added at the end.
|
||||
remove(key)
|
||||
add(key)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Go to a top level stack.
|
||||
*/
|
||||
private fun goToTopLevel(key: NavKey) {
|
||||
state.topLevelStack.apply {
|
||||
if (key == state.startKey) {
|
||||
// This is the start key. Clear the stack so it's added as the only key.
|
||||
clear()
|
||||
} else {
|
||||
// Remove it if it's already in the stack so it's added at the end.
|
||||
remove(key)
|
||||
}
|
||||
add(key)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Go up in the current sub stack by clearing all but the root key.
|
||||
*/
|
||||
private fun goUp() {
|
||||
state.currentSubStack.run {
|
||||
if (size > 1) subList(1, size).clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,90 +0,0 @@
|
||||
/*
|
||||
* Copyright 2025 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
|
||||
*
|
||||
* https://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 com.google.samples.apps.nowinandroid.core.navigation
|
||||
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
import androidx.compose.runtime.snapshots.SnapshotStateList
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.serialization.saved
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.savedstate.serialization.SavedStateConfiguration
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.builtins.ListSerializer
|
||||
import kotlinx.serialization.builtins.MapSerializer
|
||||
import kotlinx.serialization.modules.SerializersModule
|
||||
import kotlinx.serialization.serializer
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* TODO: I'm not sure why this needs to be a ViewModel - why can't it be a plain state holder that
|
||||
* is scoped to `NiaAppState`?
|
||||
* https://github.com/android/nav3-recipes/blob/main/app/src/main/java/com/example/nav3recipes/multiplestacks/NavigationState.kt#L71
|
||||
*
|
||||
*/
|
||||
/*
|
||||
@HiltViewModel
|
||||
class NiaBackStackViewModel @Inject constructor(
|
||||
savedStateHandle: SavedStateHandle,
|
||||
val niaNavigationState: NiaNavigationState,
|
||||
serializersModules: SerializersModule,
|
||||
) : ViewModel() {
|
||||
|
||||
private val config = SavedStateConfiguration { serializersModule = serializersModules }
|
||||
|
||||
@VisibleForTesting
|
||||
internal var backStackMap by savedStateHandle.saved(
|
||||
serializer = MapSerializer(
|
||||
serializer<NiaNavKey>(),
|
||||
serializer<List<NiaNavKey>>(),
|
||||
),
|
||||
configuration = config,
|
||||
) {
|
||||
linkedMapOf()
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
internal var activeTopLeveLKeys by savedStateHandle.saved(
|
||||
serializer = ListSerializer(serializer<NiaNavKey>()),
|
||||
configuration = config,
|
||||
) {
|
||||
listOf()
|
||||
}
|
||||
|
||||
init {
|
||||
if (backStackMap.isNotEmpty()) {
|
||||
// Restore backstack from saved state handle if not empty
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
niaNavigationState.restore(
|
||||
activeTopLeveLKeys,
|
||||
backStackMap as LinkedHashMap<NiaNavKey, SnapshotStateList<NiaNavKey>>,
|
||||
)
|
||||
}
|
||||
|
||||
// Start observing changes to the backStack and save backStack whenever it updates
|
||||
viewModelScope.launch {
|
||||
snapshotFlow {
|
||||
activeTopLeveLKeys = niaNavigationState.activeTopLeveLKeys.toList()
|
||||
backStackMap = niaNavigationState.backStacks
|
||||
}.collect()
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
@ -1,173 +0,0 @@
|
||||
/*
|
||||
* Copyright 2025 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
|
||||
*
|
||||
* https://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 com.google.samples.apps.nowinandroid.core.navigation
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.key
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.snapshots.SnapshotStateList
|
||||
import androidx.compose.runtime.toMutableStateList
|
||||
import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator
|
||||
import androidx.navigation3.runtime.EntryProviderScope
|
||||
import androidx.navigation3.runtime.NavEntry
|
||||
import androidx.navigation3.runtime.entryProvider
|
||||
import androidx.navigation3.runtime.rememberDecoratedNavEntries
|
||||
import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator
|
||||
import org.jetbrains.annotations.VisibleForTesting
|
||||
import javax.inject.Inject
|
||||
import kotlin.collections.plus
|
||||
|
||||
// TODO: Consider changing this to `NiaNavigationState`
|
||||
class NiaNavigationState(
|
||||
internal val startKey: NiaNavKey,
|
||||
) {
|
||||
internal var backStacks: MutableMap<NiaNavKey, SnapshotStateList<NiaNavKey>> =
|
||||
linkedMapOf(
|
||||
startKey to mutableStateListOf(startKey),
|
||||
)
|
||||
|
||||
val activeTopLeveLKeys: SnapshotStateList<NiaNavKey> = mutableStateListOf(startKey)
|
||||
|
||||
var currentTopLevelKey: NiaNavKey by mutableStateOf(activeTopLeveLKeys.last())
|
||||
private set
|
||||
|
||||
@get:VisibleForTesting
|
||||
val currentBackStack: List<NiaNavKey>
|
||||
get() = activeTopLeveLKeys.fold(mutableListOf()) { list, topLevelKey ->
|
||||
list.apply {
|
||||
addAll(backStacks[topLevelKey] ?: error("No back stack found for $topLevelKey"))
|
||||
}
|
||||
}
|
||||
|
||||
@get:VisibleForTesting
|
||||
val currentKey: NiaNavKey
|
||||
get() = backStacks[currentTopLevelKey]!!.last()
|
||||
|
||||
internal fun updateActiveTopLevelKeys(activeKeys: List<NiaNavKey>) {
|
||||
check(activeKeys.isNotEmpty()) { "List of active top-level keys should not be empty" }
|
||||
activeTopLeveLKeys.clear()
|
||||
activeTopLeveLKeys.addAll(activeKeys)
|
||||
currentTopLevelKey = activeTopLeveLKeys.last()
|
||||
}
|
||||
|
||||
internal fun restore(activeKeys: List<NiaNavKey>, map: LinkedHashMap<NiaNavKey, SnapshotStateList<NiaNavKey>>?) {
|
||||
map ?: return
|
||||
backStacks.clear()
|
||||
map.forEach { entry ->
|
||||
backStacks[entry.key] = entry.value.toMutableStateList()
|
||||
}
|
||||
updateActiveTopLevelKeys(activeKeys)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Document this
|
||||
*/
|
||||
class NiaNavigator @Inject constructor(
|
||||
val niaNavigationState: NiaNavigationState,
|
||||
) {
|
||||
// TODO: I wonder if it'd be simpler to have separate methods
|
||||
// for navigating to a graph and navigating to a key. If the key is on a separate graph then
|
||||
// navigate to that graph first.
|
||||
fun navigate(key: NiaNavKey) {
|
||||
val currentActiveSubStacks = linkedSetOf<NiaNavKey>()
|
||||
niaNavigationState.apply {
|
||||
currentActiveSubStacks.addAll(activeTopLeveLKeys)
|
||||
when {
|
||||
// top level singleTop -> clear substack
|
||||
key == currentTopLevelKey -> {
|
||||
backStacks[key] = mutableStateListOf(key)
|
||||
// no change to currentActiveTabs
|
||||
}
|
||||
// top level non-singleTop
|
||||
key.isTopLevel -> {
|
||||
// if navigating back to start destination, then only show the starting substack
|
||||
if (key == startKey) {
|
||||
currentActiveSubStacks.clear()
|
||||
currentActiveSubStacks.add(key)
|
||||
} else {
|
||||
// else either restore an existing substack or initiate new one
|
||||
backStacks[key] = backStacks.remove(key) ?: mutableStateListOf(key)
|
||||
// move this top level key to the top of active substacks
|
||||
currentActiveSubStacks.remove(key)
|
||||
currentActiveSubStacks.add(key)
|
||||
}
|
||||
}
|
||||
// not top level - add to current substack
|
||||
else -> {
|
||||
val currentStack = backStacks[currentTopLevelKey]!!
|
||||
// single top
|
||||
if (currentStack.lastOrNull() == key) {
|
||||
currentStack.removeLastOrNull()
|
||||
}
|
||||
currentStack.add(key)
|
||||
// no change to currentActiveTabs
|
||||
}
|
||||
}
|
||||
updateActiveTopLevelKeys(currentActiveSubStacks.toList())
|
||||
}
|
||||
}
|
||||
|
||||
fun pop() {
|
||||
niaNavigationState.apply {
|
||||
val currentSubstack = backStacks[currentTopLevelKey]!!
|
||||
if (currentSubstack.size == 1) {
|
||||
// if current sub-stack only has one key, remove the sub-stack from the map
|
||||
currentSubstack.removeLastOrNull()
|
||||
backStacks.remove(currentTopLevelKey)
|
||||
updateActiveTopLevelKeys(activeTopLeveLKeys.dropLast(1))
|
||||
} else {
|
||||
currentSubstack.removeLastOrNull()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: I wonder if removing this would remove the need for the serializers modules
|
||||
interface NiaNavKey {
|
||||
val isTopLevel: Boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the navigation state to `NavEntry`s that can be displayed by a `NavDisplay`
|
||||
*/
|
||||
@Composable
|
||||
fun NiaNavigationState.toEntries(
|
||||
// TODO: Might be better to pass this in fully constructed
|
||||
entryProviderBuilders: Set<EntryProviderScope<NiaNavKey>.() -> Unit>,
|
||||
): List<NavEntry<NiaNavKey>> =
|
||||
activeTopLeveLKeys.fold(emptyList()) { entries, topLevelKey ->
|
||||
val decorated = key(topLevelKey) {
|
||||
val decorators = listOf(
|
||||
rememberSaveableStateHolderNavEntryDecorator(),
|
||||
rememberViewModelStoreNavEntryDecorator<NiaNavKey>(),
|
||||
)
|
||||
rememberDecoratedNavEntries(
|
||||
backStack = backStacks[topLevelKey]!!,
|
||||
entryDecorators = decorators,
|
||||
entryProvider = entryProvider {
|
||||
entryProviderBuilders.forEach { builder ->
|
||||
builder()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
entries + decorated
|
||||
}
|
||||
@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Copyright 2025 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 com.google.samples.apps.nowinandroid.core.navigation.simple
|
||||
|
||||
import androidx.navigation3.runtime.NavKey
|
||||
|
||||
/**
|
||||
* Handles navigation events (forward and back) by updating the navigation state.
|
||||
*/
|
||||
class Navigator(val state: NavigationState){
|
||||
fun navigate(route: NavKey){
|
||||
if (route in state.backStacks.keys){
|
||||
// This is a top level route, just switch to it
|
||||
state.topLevelRoute = route
|
||||
} else {
|
||||
state.backStacks[state.topLevelRoute]?.add(route)
|
||||
}
|
||||
}
|
||||
|
||||
fun goBack(){
|
||||
val currentStack = state.backStacks[state.topLevelRoute] ?:
|
||||
error("Stack for ${state.topLevelRoute} not found")
|
||||
val currentRoute = currentStack.last()
|
||||
|
||||
// If we're at the base of the current route, go back to the start route stack.
|
||||
if (currentRoute == state.topLevelRoute){
|
||||
state.topLevelRoute = state.startRoute
|
||||
} else {
|
||||
currentStack.removeLastOrNull()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,260 @@
|
||||
/*
|
||||
* Copyright 2025 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
|
||||
*
|
||||
* https://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 com.google.samples.apps.nowinandroid.core.navigation
|
||||
|
||||
import androidx.navigation3.runtime.NavBackStack
|
||||
import androidx.navigation3.runtime.NavKey
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
private object TestFirstTopLevelKey : NavKey
|
||||
private object TestSecondTopLevelKey : NavKey
|
||||
private object TestThirdTopLevelKey : NavKey
|
||||
private object TestKeyFirst : NavKey
|
||||
private object TestKeySecond : NavKey
|
||||
|
||||
class NavigatorTest {
|
||||
|
||||
private lateinit var navigationState: NavigationState
|
||||
private lateinit var navigator: Navigator
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
|
||||
val startKey = TestFirstTopLevelKey
|
||||
val topLevelStack = NavBackStack<NavKey>(startKey)
|
||||
val topLevelKeys = listOf(
|
||||
startKey,
|
||||
TestSecondTopLevelKey,
|
||||
TestThirdTopLevelKey
|
||||
)
|
||||
val subStacks = topLevelKeys.associateWith { key -> NavBackStack(key) }
|
||||
|
||||
navigationState = NavigationState(
|
||||
startKey = startKey,
|
||||
topLevelStack = topLevelStack,
|
||||
subStacks = subStacks
|
||||
)
|
||||
navigator = Navigator(navigationState)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStartKey() {
|
||||
assertThat(navigationState.startKey).isEqualTo(TestFirstTopLevelKey)
|
||||
assertThat(navigationState.currentTopLevelKey).isEqualTo(TestFirstTopLevelKey)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNavigate() {
|
||||
navigator.navigate(TestKeyFirst)
|
||||
|
||||
assertThat(navigationState.currentTopLevelKey).isEqualTo(TestFirstTopLevelKey)
|
||||
assertThat(navigationState.subStacks[TestFirstTopLevelKey]?.last()).isEqualTo(TestKeyFirst)
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNavigateTopLevel() {
|
||||
navigator.navigate(TestSecondTopLevelKey)
|
||||
assertThat(navigationState.currentTopLevelKey).isEqualTo(TestSecondTopLevelKey)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNavigateSingleTop() {
|
||||
navigator.navigate(TestKeyFirst)
|
||||
|
||||
assertThat(navigationState.currentSubStack).containsExactly(
|
||||
TestFirstTopLevelKey,
|
||||
TestKeyFirst,
|
||||
).inOrder()
|
||||
|
||||
navigator.navigate(TestKeyFirst)
|
||||
|
||||
assertThat(navigationState.currentSubStack).containsExactly(
|
||||
TestFirstTopLevelKey,
|
||||
TestKeyFirst,
|
||||
).inOrder()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNavigateTopLevelSingleTop() {
|
||||
navigator.navigate(TestSecondTopLevelKey)
|
||||
navigator.navigate(TestKeyFirst)
|
||||
|
||||
assertThat(navigationState.currentSubStack).containsExactly(
|
||||
TestSecondTopLevelKey,
|
||||
TestKeyFirst,
|
||||
).inOrder()
|
||||
|
||||
navigator.navigate(TestSecondTopLevelKey)
|
||||
|
||||
assertThat(navigationState.currentSubStack).containsExactly(
|
||||
TestSecondTopLevelKey,
|
||||
).inOrder()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSubStack() {
|
||||
navigator.navigate(TestKeyFirst)
|
||||
|
||||
assertThat(navigationState.currentKey).isEqualTo(TestKeyFirst)
|
||||
assertThat(navigationState.currentTopLevelKey).isEqualTo(TestFirstTopLevelKey)
|
||||
|
||||
navigator.navigate(TestKeySecond)
|
||||
|
||||
assertThat(navigationState.currentKey).isEqualTo(TestKeySecond)
|
||||
assertThat(navigationState.currentTopLevelKey).isEqualTo(TestFirstTopLevelKey)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMultiStack() {
|
||||
// add to start stack
|
||||
navigator.navigate(TestKeyFirst)
|
||||
|
||||
assertThat(navigationState.currentKey).isEqualTo(TestKeyFirst)
|
||||
assertThat(navigationState.currentTopLevelKey).isEqualTo(TestFirstTopLevelKey)
|
||||
|
||||
// navigate to new top level
|
||||
navigator.navigate(TestSecondTopLevelKey)
|
||||
|
||||
assertThat(navigationState.currentKey).isEqualTo(TestSecondTopLevelKey)
|
||||
assertThat(navigationState.currentTopLevelKey).isEqualTo(TestSecondTopLevelKey)
|
||||
|
||||
// add to new stack
|
||||
navigator.navigate(TestKeySecond)
|
||||
|
||||
assertThat(navigationState.currentKey).isEqualTo(TestKeySecond)
|
||||
assertThat(navigationState.currentTopLevelKey).isEqualTo(TestSecondTopLevelKey)
|
||||
|
||||
// go back to start stack
|
||||
navigator.navigate(TestFirstTopLevelKey)
|
||||
|
||||
assertThat(navigationState.currentKey).isEqualTo(TestKeyFirst)
|
||||
assertThat(navigationState.currentTopLevelKey).isEqualTo(TestFirstTopLevelKey)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPopOneNonTopLevel() {
|
||||
navigator.navigate(TestKeyFirst)
|
||||
navigator.navigate(TestKeySecond)
|
||||
|
||||
assertThat(navigationState.currentSubStack).containsExactly(
|
||||
TestFirstTopLevelKey,
|
||||
TestKeyFirst,
|
||||
TestKeySecond,
|
||||
).inOrder()
|
||||
|
||||
navigator.goBack()
|
||||
|
||||
assertThat(navigationState.currentSubStack).containsExactly(
|
||||
TestFirstTopLevelKey,
|
||||
TestKeyFirst,
|
||||
).inOrder()
|
||||
|
||||
assertThat(navigationState.currentKey).isEqualTo(TestKeyFirst)
|
||||
assertThat(navigationState.currentTopLevelKey).isEqualTo(TestFirstTopLevelKey)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPopOneTopLevel() {
|
||||
navigator.navigate(TestKeyFirst)
|
||||
navigator.navigate(TestSecondTopLevelKey)
|
||||
|
||||
assertThat(navigationState.currentSubStack).containsExactly(
|
||||
TestSecondTopLevelKey,
|
||||
).inOrder()
|
||||
|
||||
assertThat(navigationState.currentKey).isEqualTo(TestSecondTopLevelKey)
|
||||
assertThat(navigationState.currentTopLevelKey).isEqualTo(TestSecondTopLevelKey)
|
||||
|
||||
// remove TopLevel
|
||||
navigator.goBack()
|
||||
|
||||
assertThat(navigationState.currentSubStack).containsExactly(
|
||||
TestFirstTopLevelKey,
|
||||
TestKeyFirst,
|
||||
).inOrder()
|
||||
|
||||
assertThat(navigationState.currentKey).isEqualTo(TestKeyFirst)
|
||||
assertThat(navigationState.currentTopLevelKey).isEqualTo(TestFirstTopLevelKey)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun popMultipleNonTopLevel() {
|
||||
navigator.navigate(TestKeyFirst)
|
||||
navigator.navigate(TestKeySecond)
|
||||
|
||||
assertThat(navigationState.currentSubStack).containsExactly(
|
||||
TestFirstTopLevelKey,
|
||||
TestKeyFirst,
|
||||
TestKeySecond,
|
||||
).inOrder()
|
||||
|
||||
navigator.goBack()
|
||||
navigator.goBack()
|
||||
|
||||
assertThat(navigationState.currentSubStack).containsExactly(
|
||||
TestFirstTopLevelKey,
|
||||
).inOrder()
|
||||
|
||||
assertThat(navigationState.currentKey).isEqualTo(TestFirstTopLevelKey)
|
||||
assertThat(navigationState.currentTopLevelKey).isEqualTo(TestFirstTopLevelKey)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun popMultipleTopLevel() {
|
||||
// second sub-stack
|
||||
navigator.navigate(TestSecondTopLevelKey)
|
||||
navigator.navigate(TestKeyFirst)
|
||||
|
||||
assertThat(navigationState.currentSubStack).containsExactly(
|
||||
TestSecondTopLevelKey,
|
||||
TestKeyFirst,
|
||||
).inOrder()
|
||||
|
||||
// third sub-stack
|
||||
navigator.navigate(TestThirdTopLevelKey)
|
||||
navigator.navigate(TestKeySecond)
|
||||
|
||||
assertThat(navigationState.currentSubStack).containsExactly(
|
||||
TestThirdTopLevelKey,
|
||||
TestKeySecond,
|
||||
).inOrder()
|
||||
|
||||
repeat(4) {
|
||||
navigator.goBack()
|
||||
}
|
||||
|
||||
assertThat(navigationState.currentSubStack).containsExactly(
|
||||
TestFirstTopLevelKey,
|
||||
).inOrder()
|
||||
|
||||
assertThat(navigationState.currentKey).isEqualTo(TestFirstTopLevelKey)
|
||||
assertThat(navigationState.currentTopLevelKey).isEqualTo(TestFirstTopLevelKey)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun throwOnEmptyBackStack() {
|
||||
assertFailsWith<IllegalStateException> {
|
||||
navigator.goBack()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,287 +0,0 @@
|
||||
/*
|
||||
* Copyright 2025 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
|
||||
*
|
||||
* https://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 com.google.samples.apps.nowinandroid.core.navigation
|
||||
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
class NiaNavigatorStateTest {
|
||||
|
||||
private lateinit var niaNavigationState: NiaNavigationState
|
||||
private lateinit var niaNavigator: NiaNavigator
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
niaNavigationState = NiaNavigationState(TestStartKey)
|
||||
niaNavigator = NiaNavigator(niaNavigationState)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStartKey() {
|
||||
assertThat(niaNavigationState.currentKey).isEqualTo(TestStartKey)
|
||||
assertThat(niaNavigationState.currentTopLevelKey).isEqualTo(TestStartKey)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNavigate() {
|
||||
niaNavigator.navigate(TestKeyFirst)
|
||||
|
||||
assertThat(niaNavigationState.currentKey).isEqualTo(TestKeyFirst)
|
||||
assertThat(niaNavigationState.currentTopLevelKey).isEqualTo(TestStartKey)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNavigateTopLevel() {
|
||||
niaNavigator.navigate(TestTopLevelKey)
|
||||
|
||||
assertThat(niaNavigationState.currentKey).isEqualTo(TestTopLevelKey)
|
||||
assertThat(niaNavigationState.currentTopLevelKey).isEqualTo(TestTopLevelKey)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNavigateSingleTop() {
|
||||
niaNavigator.navigate(TestKeyFirst)
|
||||
|
||||
assertThat(niaNavigationState.currentBackStack).containsExactly(
|
||||
TestStartKey,
|
||||
TestKeyFirst,
|
||||
).inOrder()
|
||||
|
||||
niaNavigator.navigate(TestKeyFirst)
|
||||
|
||||
assertThat(niaNavigationState.currentBackStack).containsExactly(
|
||||
TestStartKey,
|
||||
TestKeyFirst,
|
||||
).inOrder()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNavigateTopLevelSingleTop() {
|
||||
niaNavigator.navigate(TestTopLevelKey)
|
||||
niaNavigator.navigate(TestKeyFirst)
|
||||
|
||||
assertThat(niaNavigationState.currentBackStack).containsExactly(
|
||||
TestStartKey,
|
||||
TestTopLevelKey,
|
||||
TestKeyFirst,
|
||||
).inOrder()
|
||||
|
||||
niaNavigator.navigate(TestTopLevelKey)
|
||||
|
||||
assertThat(niaNavigationState.currentBackStack).containsExactly(
|
||||
TestStartKey,
|
||||
TestTopLevelKey,
|
||||
).inOrder()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSubStack() {
|
||||
niaNavigator.navigate(TestKeyFirst)
|
||||
|
||||
assertThat(niaNavigationState.currentKey).isEqualTo(TestKeyFirst)
|
||||
assertThat(niaNavigationState.currentTopLevelKey).isEqualTo(TestStartKey)
|
||||
|
||||
niaNavigator.navigate(TestKeySecond)
|
||||
|
||||
assertThat(niaNavigationState.currentKey).isEqualTo(TestKeySecond)
|
||||
assertThat(niaNavigationState.currentTopLevelKey).isEqualTo(TestStartKey)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMultiStack() {
|
||||
// add to start stack
|
||||
niaNavigator.navigate(TestKeyFirst)
|
||||
|
||||
assertThat(niaNavigationState.currentKey).isEqualTo(TestKeyFirst)
|
||||
assertThat(niaNavigationState.currentTopLevelKey).isEqualTo(TestStartKey)
|
||||
|
||||
// navigate to new top level
|
||||
niaNavigator.navigate(TestTopLevelKey)
|
||||
|
||||
assertThat(niaNavigationState.currentKey).isEqualTo(TestTopLevelKey)
|
||||
assertThat(niaNavigationState.currentTopLevelKey).isEqualTo(TestTopLevelKey)
|
||||
|
||||
// add to new stack
|
||||
niaNavigator.navigate(TestKeySecond)
|
||||
|
||||
assertThat(niaNavigationState.currentKey).isEqualTo(TestKeySecond)
|
||||
assertThat(niaNavigationState.currentTopLevelKey).isEqualTo(TestTopLevelKey)
|
||||
|
||||
// go back to start stack
|
||||
niaNavigator.navigate(TestStartKey)
|
||||
|
||||
assertThat(niaNavigationState.currentKey).isEqualTo(TestKeyFirst)
|
||||
assertThat(niaNavigationState.currentTopLevelKey).isEqualTo(TestStartKey)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRestore() {
|
||||
assertThat(niaNavigationState.currentBackStack).containsExactly(TestStartKey)
|
||||
|
||||
niaNavigationState.restore(
|
||||
listOf(TestStartKey, TestTopLevelKey),
|
||||
linkedMapOf(
|
||||
TestStartKey to mutableStateListOf(TestStartKey, TestKeyFirst),
|
||||
TestTopLevelKey to mutableStateListOf(TestTopLevelKey, TestKeySecond),
|
||||
),
|
||||
)
|
||||
|
||||
assertThat(niaNavigationState.currentBackStack).containsExactly(
|
||||
TestStartKey,
|
||||
TestKeyFirst,
|
||||
TestTopLevelKey,
|
||||
TestKeySecond,
|
||||
).inOrder()
|
||||
|
||||
assertThat(niaNavigationState.currentKey).isEqualTo(TestKeySecond)
|
||||
assertThat(niaNavigationState.currentTopLevelKey).isEqualTo(TestTopLevelKey)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPopOneNonTopLevel() {
|
||||
niaNavigator.navigate(TestKeyFirst)
|
||||
niaNavigator.navigate(TestKeySecond)
|
||||
|
||||
assertThat(niaNavigationState.currentBackStack).containsExactly(
|
||||
TestStartKey,
|
||||
TestKeyFirst,
|
||||
TestKeySecond,
|
||||
).inOrder()
|
||||
|
||||
niaNavigator.pop()
|
||||
|
||||
assertThat(niaNavigationState.currentBackStack).containsExactly(
|
||||
TestStartKey,
|
||||
TestKeyFirst,
|
||||
).inOrder()
|
||||
|
||||
assertThat(niaNavigationState.currentKey).isEqualTo(TestKeyFirst)
|
||||
assertThat(niaNavigationState.currentTopLevelKey).isEqualTo(TestStartKey)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPopOneTopLevel() {
|
||||
niaNavigator.navigate(TestKeyFirst)
|
||||
niaNavigator.navigate(TestTopLevelKey)
|
||||
|
||||
assertThat(niaNavigationState.currentBackStack).containsExactly(
|
||||
TestStartKey,
|
||||
TestKeyFirst,
|
||||
TestTopLevelKey,
|
||||
).inOrder()
|
||||
|
||||
assertThat(niaNavigationState.currentKey).isEqualTo(TestTopLevelKey)
|
||||
assertThat(niaNavigationState.currentTopLevelKey).isEqualTo(TestTopLevelKey)
|
||||
|
||||
// remove TopLevel
|
||||
niaNavigator.pop()
|
||||
|
||||
assertThat(niaNavigationState.currentBackStack).containsExactly(
|
||||
TestStartKey,
|
||||
TestKeyFirst,
|
||||
).inOrder()
|
||||
|
||||
assertThat(niaNavigationState.currentKey).isEqualTo(TestKeyFirst)
|
||||
assertThat(niaNavigationState.currentTopLevelKey).isEqualTo(TestStartKey)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun popMultipleNonTopLevel() {
|
||||
niaNavigator.navigate(TestKeyFirst)
|
||||
niaNavigator.navigate(TestKeySecond)
|
||||
|
||||
assertThat(niaNavigationState.currentBackStack).containsExactly(
|
||||
TestStartKey,
|
||||
TestKeyFirst,
|
||||
TestKeySecond,
|
||||
).inOrder()
|
||||
|
||||
niaNavigator.pop()
|
||||
niaNavigator.pop()
|
||||
|
||||
assertThat(niaNavigationState.currentBackStack).containsExactly(
|
||||
TestStartKey,
|
||||
).inOrder()
|
||||
|
||||
assertThat(niaNavigationState.currentKey).isEqualTo(TestStartKey)
|
||||
assertThat(niaNavigationState.currentTopLevelKey).isEqualTo(TestStartKey)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun popMultipleTopLevel() {
|
||||
val testTopLevelKeyTwo = object : NiaNavKey {
|
||||
override val isTopLevel: Boolean
|
||||
get() = true
|
||||
}
|
||||
|
||||
// second sub-stack
|
||||
niaNavigator.navigate(TestTopLevelKey)
|
||||
niaNavigator.navigate(TestKeyFirst)
|
||||
// third sub-stack
|
||||
niaNavigator.navigate(testTopLevelKeyTwo)
|
||||
niaNavigator.navigate(TestKeySecond)
|
||||
|
||||
assertThat(niaNavigationState.currentBackStack).containsExactly(
|
||||
TestStartKey,
|
||||
TestTopLevelKey,
|
||||
TestKeyFirst,
|
||||
testTopLevelKeyTwo,
|
||||
TestKeySecond,
|
||||
).inOrder()
|
||||
|
||||
repeat(4) {
|
||||
niaNavigator.pop()
|
||||
}
|
||||
|
||||
assertThat(niaNavigationState.currentBackStack).containsExactly(
|
||||
TestStartKey,
|
||||
).inOrder()
|
||||
|
||||
assertThat(niaNavigationState.currentKey).isEqualTo(TestStartKey)
|
||||
assertThat(niaNavigationState.currentTopLevelKey).isEqualTo(TestStartKey)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun throwOnEmptyBackStack() {
|
||||
assertFailsWith<IllegalStateException> {
|
||||
niaNavigator.pop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private object TestStartKey : NiaNavKey {
|
||||
override val isTopLevel: Boolean
|
||||
get() = true
|
||||
}
|
||||
|
||||
private object TestTopLevelKey : NiaNavKey {
|
||||
override val isTopLevel: Boolean
|
||||
get() = true
|
||||
}
|
||||
|
||||
private object TestKeyFirst : NiaNavKey {
|
||||
override val isTopLevel: Boolean
|
||||
get() = false
|
||||
}
|
||||
|
||||
private object TestKeySecond : NiaNavKey {
|
||||
override val isTopLevel: Boolean
|
||||
get() = false
|
||||
}
|
||||
@ -1,42 +0,0 @@
|
||||
/*
|
||||
* Copyright 2025 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
|
||||
*
|
||||
* https://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 com.google.samples.apps.nowinandroid.feature.bookmarks.impl.navigation
|
||||
|
||||
import com.google.samples.apps.nowinandroid.core.navigation.NiaNavKey
|
||||
import com.google.samples.apps.nowinandroid.feature.bookmarks.api.navigation.BookmarksRoute
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import dagger.multibindings.IntoSet
|
||||
import kotlinx.serialization.modules.PolymorphicModuleBuilder
|
||||
|
||||
/**
|
||||
* Provides the DSL to register the route's [kotlinx.serialization.KSerializer] as a polymorphic serializer
|
||||
*
|
||||
*/
|
||||
/*
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object BookmarksSerializerModule {
|
||||
@Provides
|
||||
@IntoSet
|
||||
fun provideBookmarksPolymorphicModuleBuilder(): PolymorphicModuleBuilder<@JvmSuppressWildcards NiaNavKey>.() -> Unit = {
|
||||
subclass(BookmarksRoute::class, BookmarksRoute.serializer())
|
||||
}
|
||||
}
|
||||
*/
|
||||
@ -1,42 +0,0 @@
|
||||
/*
|
||||
* Copyright 2025 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
|
||||
*
|
||||
* https://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 com.google.samples.apps.nowinandroid.feature.foryou.impl.navigation
|
||||
|
||||
import com.google.samples.apps.nowinandroid.core.navigation.NiaNavKey
|
||||
import com.google.samples.apps.nowinandroid.feature.foryou.api.navigation.ForYouRoute
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import dagger.multibindings.IntoSet
|
||||
import kotlinx.serialization.modules.PolymorphicModuleBuilder
|
||||
|
||||
/**
|
||||
* Provides the DSL to register the route's [kotlinx.serialization.KSerializer] as a polymorphic serializer
|
||||
*
|
||||
*/
|
||||
/*
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object ForYouRouteSerializerModule {
|
||||
@Provides
|
||||
@IntoSet
|
||||
fun provideForYouPolymorphicModuleBuilder(): PolymorphicModuleBuilder<@JvmSuppressWildcards NiaNavKey>.() -> Unit = {
|
||||
subclass(ForYouRoute::class, ForYouRoute.serializer())
|
||||
}
|
||||
}
|
||||
*/
|
||||
@ -1,42 +0,0 @@
|
||||
/*
|
||||
* Copyright 2025 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
|
||||
*
|
||||
* https://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 com.google.samples.apps.nowinandroid.feature.interests.impl.navigation
|
||||
|
||||
import com.google.samples.apps.nowinandroid.core.navigation.NiaNavKey
|
||||
import com.google.samples.apps.nowinandroid.feature.interests.api.navigation.InterestsRoute
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import dagger.multibindings.IntoSet
|
||||
import kotlinx.serialization.modules.PolymorphicModuleBuilder
|
||||
|
||||
/**
|
||||
* Provides the DSL to register the route's [kotlinx.serialization.KSerializer] as a polymorphic serializer
|
||||
*
|
||||
*/
|
||||
/*
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object InterestsSerializerModule {
|
||||
@Provides
|
||||
@IntoSet
|
||||
fun provideInterestsPolymorphicModuleBuilder(): PolymorphicModuleBuilder<@JvmSuppressWildcards NiaNavKey>.() -> Unit = {
|
||||
subclass(InterestsRoute::class, InterestsRoute.serializer())
|
||||
}
|
||||
}
|
||||
*/
|
||||
@ -1,40 +0,0 @@
|
||||
/*
|
||||
* Copyright 2025 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
|
||||
*
|
||||
* https://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 com.google.samples.apps.nowinandroid.feature.search.impl.navigation
|
||||
|
||||
import com.google.samples.apps.nowinandroid.core.navigation.NiaNavKey
|
||||
import com.google.samples.apps.nowinandroid.feature.search.api.navigation.SearchRoute
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import dagger.multibindings.IntoSet
|
||||
import kotlinx.serialization.modules.PolymorphicModuleBuilder
|
||||
|
||||
/**
|
||||
* Provides the DSL to register the route's [kotlinx.serialization.KSerializer] as a polymorphic serializer
|
||||
*
|
||||
*/
|
||||
/*@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object SearchSerializerModule {
|
||||
@Provides
|
||||
@IntoSet
|
||||
fun provideSearchPolymorphicModuleBuilder(): PolymorphicModuleBuilder<@JvmSuppressWildcards NiaNavKey>.() -> Unit = {
|
||||
subclass(SearchRoute::class, SearchRoute.serializer())
|
||||
}
|
||||
}*/
|
||||
@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Copyright 2025 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
|
||||
*
|
||||
* https://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 com.google.samples.apps.nowinandroid.feature.topic.api.navigation
|
||||
|
||||
import com.google.samples.apps.nowinandroid.core.navigation.NiaNavKey
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import dagger.multibindings.IntoSet
|
||||
import kotlinx.serialization.modules.PolymorphicModuleBuilder
|
||||
|
||||
/**
|
||||
* Provides the DSL to register the route's [kotlinx.serialization.KSerializer] as a polymorphic serializer
|
||||
*
|
||||
*/
|
||||
/*@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object TopicSerializerModule {
|
||||
@Provides
|
||||
@IntoSet
|
||||
fun provideTopicPolymorphicModuleBuilder(): PolymorphicModuleBuilder<@JvmSuppressWildcards NiaNavKey>.() -> Unit = {
|
||||
subclass(TopicRoute::class, TopicRoute.serializer())
|
||||
}
|
||||
}*/
|
||||
Loading…
Reference in new issue