WIP remove DI

pull/2003/head
Don Turner 9 months ago
parent adcc3871be
commit c9bba49957

@ -33,6 +33,7 @@ import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle import androidx.lifecycle.repeatOnLifecycle
import androidx.metrics.performance.JankStats import androidx.metrics.performance.JankStats
import androidx.navigation3.runtime.EntryProviderScope import androidx.navigation3.runtime.EntryProviderScope
import androidx.navigation3.runtime.NavKey
import androidx.tracing.trace import androidx.tracing.trace
import com.google.samples.apps.nowinandroid.MainActivityUiState.Loading import com.google.samples.apps.nowinandroid.MainActivityUiState.Loading
import com.google.samples.apps.nowinandroid.core.analytics.AnalyticsHelper import com.google.samples.apps.nowinandroid.core.analytics.AnalyticsHelper
@ -41,7 +42,7 @@ import com.google.samples.apps.nowinandroid.core.data.repository.UserNewsResourc
import com.google.samples.apps.nowinandroid.core.data.util.NetworkMonitor import com.google.samples.apps.nowinandroid.core.data.util.NetworkMonitor
import com.google.samples.apps.nowinandroid.core.data.util.TimeZoneMonitor import com.google.samples.apps.nowinandroid.core.data.util.TimeZoneMonitor
import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme
import com.google.samples.apps.nowinandroid.core.navigation.NiaBackStackViewModel //import com.google.samples.apps.nowinandroid.core.navigation.NiaBackStackViewModel
import com.google.samples.apps.nowinandroid.core.navigation.NiaNavKey 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.NiaNavigator
import com.google.samples.apps.nowinandroid.core.ui.LocalTimeZone import com.google.samples.apps.nowinandroid.core.ui.LocalTimeZone
@ -77,17 +78,17 @@ class MainActivity : ComponentActivity() {
@Inject @Inject
lateinit var userNewsResourceRepository: UserNewsResourceRepository lateinit var userNewsResourceRepository: UserNewsResourceRepository
@Inject /*@Inject
lateinit var entryProviderBuilders: Set<@JvmSuppressWildcards EntryProviderScope<NiaNavKey>.() -> Unit> lateinit var entryProviderBuilders: Set<@JvmSuppressWildcards EntryProviderScope<NavKey>.() -> Unit>
*/
@Inject /*@Inject
lateinit var niaNavigator: NiaNavigator lateinit var niaNavigator: NiaNavigator*/
private val viewModel: MainActivityViewModel by viewModels() private val viewModel: MainActivityViewModel by viewModels()
// TODO: This isn't used // TODO: This isn't used
private val backStackViewModel: NiaBackStackViewModel by viewModels() //private val backStackViewModel: NiaBackStackViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
val splashScreen = installSplashScreen() val splashScreen = installSplashScreen()
@ -147,11 +148,12 @@ class MainActivity : ComponentActivity() {
splashScreen.setKeepOnScreenCondition { viewModel.uiState.value.shouldKeepSplashScreen() } splashScreen.setKeepOnScreenCondition { viewModel.uiState.value.shouldKeepSplashScreen() }
setContent { setContent {
val appState = rememberNiaAppState( val appState = rememberNiaAppState(
networkMonitor = networkMonitor, networkMonitor = networkMonitor,
userNewsResourceRepository = userNewsResourceRepository, userNewsResourceRepository = userNewsResourceRepository,
timeZoneMonitor = timeZoneMonitor, timeZoneMonitor = timeZoneMonitor,
niaNavigator = niaNavigator, //niaNavigator = niaNavigator,
) )
val currentTimeZone by appState.currentTimeZone.collectAsStateWithLifecycle() val currentTimeZone by appState.currentTimeZone.collectAsStateWithLifecycle()
@ -166,8 +168,7 @@ class MainActivity : ComponentActivity() {
disableDynamicTheming = themeSettings.disableDynamicTheming, disableDynamicTheming = themeSettings.disableDynamicTheming,
) { ) {
NiaApp( NiaApp(
appState, appState
entryProviderBuilders = entryProviderBuilders,
) )
} }
} }

@ -17,7 +17,7 @@
package com.google.samples.apps.nowinandroid.di package com.google.samples.apps.nowinandroid.di
import com.google.samples.apps.nowinandroid.core.navigation.NiaNavKey import com.google.samples.apps.nowinandroid.core.navigation.NiaNavKey
import com.google.samples.apps.nowinandroid.core.navigation.NavigationState import com.google.samples.apps.nowinandroid.core.navigation.NiaNavigationState
import com.google.samples.apps.nowinandroid.navigation.TopLevelDestination import com.google.samples.apps.nowinandroid.navigation.TopLevelDestination
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
@ -30,14 +30,19 @@ import javax.inject.Singleton
// TODO: Rename to `NiaNavigationStateProvider` // TODO: Rename to `NiaNavigationStateProvider`
// Does this even need to be injected? Can't we just instantiate it directly using `rememberNavigationState`? // Does this even need to be injected? Can't we just instantiate it directly using `rememberNavigationState`?
/*
@Module @Module
@InstallIn(SingletonComponent::class) @InstallIn(SingletonComponent::class)
object NavigationStateProvider { object NavigationStateProvider {
@Provides @Provides
@Singleton @Singleton
fun provideNavigationState(): NavigationState = fun provideNavigationState(): NiaNavigationState =
NavigationState( NiaNavigationState(
startKey = TopLevelDestination.FOR_YOU.key, //startKey = TopLevelDestination.FOR_YOU.key,
startKey = object : NiaNavKey {
override val isTopLevel: Boolean
get() = true
}
) )
// TODO: Remove commented out code // TODO: Remove commented out code
@ -49,11 +54,13 @@ object NavigationStateProvider {
// ): NiaNavigator = // ): NiaNavigator =
// NiaNavigator(state) // NiaNavigator(state)
/** */
/**
* Registers feature modules' polymorphic serializers to support * Registers feature modules' polymorphic serializers to support
* feature keys' save and restore by savedstate * feature keys' save and restore by savedstate
* in [com.google.samples.apps.nowinandroid.core.navigation.NiaBackStackViewModel]. * in [com.google.samples.apps.nowinandroid.core.navigation.NiaBackStackViewModel].
*/ *//*
@Provides @Provides
@Singleton @Singleton
fun provideSerializersModule( fun provideSerializersModule(
@ -64,3 +71,4 @@ object NavigationStateProvider {
} }
} }
} }
*/

@ -32,7 +32,7 @@ fun NiaNavDisplay(
entryProviderBuilders: Set<EntryProviderScope<NiaNavKey>.() -> Unit>, entryProviderBuilders: Set<EntryProviderScope<NiaNavKey>.() -> Unit>,
) { ) {
val listDetailStrategy = rememberListDetailSceneStrategy<NiaNavKey>() val listDetailStrategy = rememberListDetailSceneStrategy<NiaNavKey>()
val entries = niaNavigator.navigationState.toEntries(entryProviderBuilders) val entries = niaNavigator.niaNavigationState.toEntries(entryProviderBuilders)
NavDisplay( NavDisplay(
entries = entries, entries = entries,
sceneStrategy = listDetailStrategy, sceneStrategy = listDetailStrategy,

@ -18,9 +18,9 @@ package com.google.samples.apps.nowinandroid.navigation
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.compose.ui.graphics.vector.ImageVector 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.R
import com.google.samples.apps.nowinandroid.core.designsystem.icon.NiaIcons import com.google.samples.apps.nowinandroid.core.designsystem.icon.NiaIcons
import com.google.samples.apps.nowinandroid.core.navigation.NiaNavKey
import com.google.samples.apps.nowinandroid.feature.bookmarks.api.navigation.BookmarksRoute 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.foryou.api.navigation.ForYouRoute
import com.google.samples.apps.nowinandroid.feature.interests.api.navigation.InterestsRoute import com.google.samples.apps.nowinandroid.feature.interests.api.navigation.InterestsRoute
@ -43,6 +43,7 @@ import com.google.samples.apps.nowinandroid.feature.search.api.R as searchR
* @param baseRoute The highest ancestor of this destination. Defaults to [route], meaning that * @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). * there is a single destination in that section of the app (no nested destinations).
*/ */
/*
enum class TopLevelDestination( enum class TopLevelDestination(
val selectedIcon: ImageVector, val selectedIcon: ImageVector,
val unselectedIcon: ImageVector, val unselectedIcon: ImageVector,
@ -78,3 +79,62 @@ enum class TopLevelDestination(
} }
internal val TopLevelDestinations = TopLevelDestination.entries.associateBy { dest -> dest.key } internal val TopLevelDestinations = TopLevelDestination.entries.associateBy { dest -> dest.key }
*/
val FOR_YOU = TopLevelDestination(
selectedIcon = NiaIcons.Upcoming,
unselectedIcon = NiaIcons.UpcomingBorder,
iconTextId = forYouR.string.feature_foryou_api_title,
titleTextId = R.string.app_name,
route = ForYouRoute::class,
key = ForYouRoute,
)
val BOOKMARKS = TopLevelDestination(
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,
)
val INTERESTS = TopLevelDestination(
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)
)
val TOP_LEVEL_ROUTES = mapOf<NavKey, TopLevelDestination>(
ForYouRoute to FOR_YOU,
BookmarksRoute to BOOKMARKS,
InterestsRoute(null) to INTERESTS,
)
/**
* 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).
*/
data class TopLevelDestination(
val selectedIcon: ImageVector,
val unselectedIcon: ImageVector,
@StringRes val iconTextId: Int,
@StringRes val titleTextId: Int,
val route: KClass<*>,
val key: NavKey,
)

@ -37,8 +37,10 @@ import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
import androidx.compose.material3.adaptive.WindowAdaptiveInfo import androidx.compose.material3.adaptive.WindowAdaptiveInfo
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
import androidx.compose.material3.adaptive.navigation3.rememberListDetailSceneStrategy
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
@ -60,6 +62,10 @@ import androidx.compose.ui.semantics.testTagsAsResourceId
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation3.runtime.EntryProviderScope import androidx.navigation3.runtime.EntryProviderScope
import androidx.navigation3.runtime.NavKey
import androidx.navigation3.runtime.entryProvider
import androidx.navigation3.ui.NavDisplay
import com.example.nav3recipes.multiplestacks.Navigator
import com.google.samples.apps.nowinandroid.R import com.google.samples.apps.nowinandroid.R
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaBackground import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaBackground
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaGradientBackground import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaGradientBackground
@ -69,11 +75,18 @@ import com.google.samples.apps.nowinandroid.core.designsystem.icon.NiaIcons
import com.google.samples.apps.nowinandroid.core.designsystem.theme.GradientColors import com.google.samples.apps.nowinandroid.core.designsystem.theme.GradientColors
import com.google.samples.apps.nowinandroid.core.designsystem.theme.LocalGradientColors import com.google.samples.apps.nowinandroid.core.designsystem.theme.LocalGradientColors
import com.google.samples.apps.nowinandroid.core.navigation.NiaNavKey import com.google.samples.apps.nowinandroid.core.navigation.NiaNavKey
import com.google.samples.apps.nowinandroid.core.navigation.simple.toEntries
import com.google.samples.apps.nowinandroid.core.navigation.toEntries
import com.google.samples.apps.nowinandroid.feature.bookmarks.impl.navigation.LocalSnackbarHostState import com.google.samples.apps.nowinandroid.feature.bookmarks.impl.navigation.LocalSnackbarHostState
import com.google.samples.apps.nowinandroid.feature.search.api.navigation.navigateToSearch import com.google.samples.apps.nowinandroid.feature.bookmarks.impl.navigation.bookmarksEntry
import com.google.samples.apps.nowinandroid.feature.foryou.impl.navigation.forYouEntry
import com.google.samples.apps.nowinandroid.feature.interests.impl.navigation.interestsEntry
import com.google.samples.apps.nowinandroid.feature.search.api.navigation.SearchRoute
import com.google.samples.apps.nowinandroid.feature.search.impl.navigation.searchEntry
import com.google.samples.apps.nowinandroid.feature.settings.api.SettingsDialog import com.google.samples.apps.nowinandroid.feature.settings.api.SettingsDialog
import com.google.samples.apps.nowinandroid.navigation.NiaNavDisplay import com.google.samples.apps.nowinandroid.feature.topic.impl.navigation.topicEntry
import com.google.samples.apps.nowinandroid.navigation.TopLevelDestination import com.google.samples.apps.nowinandroid.navigation.FOR_YOU
import com.google.samples.apps.nowinandroid.navigation.TOP_LEVEL_ROUTES
import com.google.samples.apps.nowinandroid.feature.settings.api.R as settingsR import com.google.samples.apps.nowinandroid.feature.settings.api.R as settingsR
@Composable @Composable
@ -81,10 +94,10 @@ fun NiaApp(
appState: NiaAppState, appState: NiaAppState,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
windowAdaptiveInfo: WindowAdaptiveInfo = currentWindowAdaptiveInfo(), windowAdaptiveInfo: WindowAdaptiveInfo = currentWindowAdaptiveInfo(),
entryProviderBuilders: Set<EntryProviderScope<NiaNavKey>.() -> Unit>, //entryProviderBuilders: Set<EntryProviderScope<NavKey>.() -> Unit>,
) { ) {
val shouldShowGradientBackground = val shouldShowGradientBackground =
appState.currentTopLevelDestination == TopLevelDestination.FOR_YOU appState.currentTopLevelDestination == FOR_YOU
var showSettingsDialog by rememberSaveable { mutableStateOf(false) } var showSettingsDialog by rememberSaveable { mutableStateOf(false) }
NiaBackground(modifier = modifier) { NiaBackground(modifier = modifier) {
@ -116,7 +129,7 @@ fun NiaApp(
onSettingsDismissed = { showSettingsDialog = false }, onSettingsDismissed = { showSettingsDialog = false },
onTopAppBarActionClick = { showSettingsDialog = true }, onTopAppBarActionClick = { showSettingsDialog = true },
windowAdaptiveInfo = windowAdaptiveInfo, windowAdaptiveInfo = windowAdaptiveInfo,
entryProviderBuilders = entryProviderBuilders, //entryProviderBuilders = entryProviderBuilders,
) )
} }
} }
@ -126,7 +139,7 @@ fun NiaApp(
@Composable @Composable
@OptIn( @OptIn(
ExperimentalMaterial3Api::class, ExperimentalMaterial3Api::class,
ExperimentalComposeUiApi::class, ExperimentalComposeUiApi::class, ExperimentalMaterial3AdaptiveApi::class,
) )
internal fun NiaApp( internal fun NiaApp(
appState: NiaAppState, appState: NiaAppState,
@ -135,7 +148,7 @@ internal fun NiaApp(
onTopAppBarActionClick: () -> Unit, onTopAppBarActionClick: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
windowAdaptiveInfo: WindowAdaptiveInfo = currentWindowAdaptiveInfo(), windowAdaptiveInfo: WindowAdaptiveInfo = currentWindowAdaptiveInfo(),
entryProviderBuilders: Set<EntryProviderScope<NiaNavKey>.() -> Unit>, //entryProviderBuilders: Set<EntryProviderScope<NavKey>.() -> Unit>,
) { ) {
val unreadDestinations by appState.topLevelDestinationsWithUnreadResources val unreadDestinations by appState.topLevelDestinationsWithUnreadResources
.collectAsStateWithLifecycle() .collectAsStateWithLifecycle()
@ -149,14 +162,16 @@ internal fun NiaApp(
val snackbarHostState = LocalSnackbarHostState.current val snackbarHostState = LocalSnackbarHostState.current
val navigator = remember { Navigator(appState.navigationState) }
NiaNavigationSuiteScaffold( NiaNavigationSuiteScaffold(
navigationSuiteItems = { navigationSuiteItems = {
appState.topLevelDestinations.forEach { destination -> TOP_LEVEL_ROUTES.values.forEach { destination ->
val hasUnread = unreadDestinations.contains(destination) val hasUnread = unreadDestinations.contains(destination)
val selected = destination.key == currentTopLevelKey val selected = destination.key == currentTopLevelKey
item( item(
selected = selected, selected = selected,
onClick = { appState.niaNavigator.navigate(destination.key) }, onClick = { navigator.navigate(destination.key) },
icon = { icon = {
Icon( Icon(
imageVector = destination.unselectedIcon, imageVector = destination.unselectedIcon,
@ -227,7 +242,7 @@ internal fun NiaApp(
containerColor = Color.Transparent, containerColor = Color.Transparent,
), ),
onActionClick = { onTopAppBarActionClick() }, onActionClick = { onTopAppBarActionClick() },
onNavigationClick = { appState.niaNavigator.navigateToSearch() }, onNavigationClick = { navigator.navigate(SearchRoute) },
) )
} }
@ -242,13 +257,20 @@ internal fun NiaApp(
), ),
) { ) {
// Instantiate the NavigationState here val listDetailStrategy = rememberListDetailSceneStrategy<NavKey>()
val entryProvider = entryProvider {
forYouEntry(navigator)
bookmarksEntry(navigator)
interestsEntry(navigator)
topicEntry(navigator)
searchEntry(navigator)
}
NiaNavDisplay( NavDisplay(
niaNavigator = appState.niaNavigator, entries = appState.navigationState.toEntries(entryProvider),
entryProviderBuilders = entryProviderBuilders, sceneStrategy = listDetailStrategy,
onBack = { navigator.goBack() },
) )
} }

@ -24,10 +24,14 @@ import com.google.samples.apps.nowinandroid.core.data.repository.UserNewsResourc
import com.google.samples.apps.nowinandroid.core.data.util.NetworkMonitor import com.google.samples.apps.nowinandroid.core.data.util.NetworkMonitor
import com.google.samples.apps.nowinandroid.core.data.util.TimeZoneMonitor import com.google.samples.apps.nowinandroid.core.data.util.TimeZoneMonitor
import com.google.samples.apps.nowinandroid.core.navigation.NiaNavigator import com.google.samples.apps.nowinandroid.core.navigation.NiaNavigator
import com.google.samples.apps.nowinandroid.core.navigation.simple.NavigationState
import com.google.samples.apps.nowinandroid.core.navigation.simple.rememberNavigationState
import com.google.samples.apps.nowinandroid.feature.foryou.api.navigation.ForYouRoute
import com.google.samples.apps.nowinandroid.navigation.BOOKMARKS
import com.google.samples.apps.nowinandroid.navigation.FOR_YOU
import com.google.samples.apps.nowinandroid.navigation.TopLevelDestination import com.google.samples.apps.nowinandroid.navigation.TopLevelDestination
import com.google.samples.apps.nowinandroid.navigation.TopLevelDestination.BOOKMARKS import com.google.samples.apps.nowinandroid.navigation.TOP_LEVEL_ROUTES
import com.google.samples.apps.nowinandroid.navigation.TopLevelDestination.FOR_YOU //import com.google.samples.apps.nowinandroid.navigation.TopLevelDestinations
import com.google.samples.apps.nowinandroid.navigation.TopLevelDestinations
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
@ -41,19 +45,24 @@ fun rememberNiaAppState(
networkMonitor: NetworkMonitor, networkMonitor: NetworkMonitor,
userNewsResourceRepository: UserNewsResourceRepository, userNewsResourceRepository: UserNewsResourceRepository,
timeZoneMonitor: TimeZoneMonitor, timeZoneMonitor: TimeZoneMonitor,
niaNavigator: NiaNavigator, //niaNavigator: NiaNavigator,
coroutineScope: CoroutineScope = rememberCoroutineScope(), coroutineScope: CoroutineScope = rememberCoroutineScope(),
): NiaAppState { ): NiaAppState {
NavigationTrackingSideEffect(niaNavigator) //NavigationTrackingSideEffect(niaNavigator)
val navigationState = rememberNavigationState(ForYouRoute, TOP_LEVEL_ROUTES.keys)
return remember( return remember(
niaNavigator, //niaNavigator,
navigationState,
coroutineScope, coroutineScope,
networkMonitor, networkMonitor,
userNewsResourceRepository, userNewsResourceRepository,
timeZoneMonitor, timeZoneMonitor,
) { ) {
NiaAppState( NiaAppState(
niaNavigator = niaNavigator, //niaNavigator = niaNavigator,
navigationState = navigationState,
coroutineScope = coroutineScope, coroutineScope = coroutineScope,
networkMonitor = networkMonitor, networkMonitor = networkMonitor,
userNewsResourceRepository = userNewsResourceRepository, userNewsResourceRepository = userNewsResourceRepository,
@ -64,14 +73,20 @@ fun rememberNiaAppState(
@Stable @Stable
class NiaAppState( class NiaAppState(
val niaNavigator: NiaNavigator, //val niaNavigator: NiaNavigator,
val navigationState: NavigationState,
coroutineScope: CoroutineScope, coroutineScope: CoroutineScope,
networkMonitor: NetworkMonitor, networkMonitor: NetworkMonitor,
userNewsResourceRepository: UserNewsResourceRepository, userNewsResourceRepository: UserNewsResourceRepository,
timeZoneMonitor: TimeZoneMonitor, timeZoneMonitor: TimeZoneMonitor,
) { ) {
/*
val currentTopLevelDestination: TopLevelDestination?
@Composable get() = TOP_LEVEL_ROUTES[niaNavigator.navigationState.currentTopLevelKey]
*/
// TODO: It seems unnecessary to expose this as a TopLevelDestination rather than just a key
val currentTopLevelDestination: TopLevelDestination? val currentTopLevelDestination: TopLevelDestination?
@Composable get() = TopLevelDestinations[niaNavigator.navigationState.currentTopLevelKey] @Composable get() = TOP_LEVEL_ROUTES[navigationState.topLevelRoute]
val isOffline = networkMonitor.isOnline val isOffline = networkMonitor.isOnline
.map(Boolean::not) .map(Boolean::not)
@ -81,12 +96,6 @@ class NiaAppState(
initialValue = false, initialValue = false,
) )
/**
* Map of top level destinations to be used in the TopBar, BottomBar and NavRail. The key is the
* route.
*/
val topLevelDestinations: List<TopLevelDestination> = TopLevelDestination.entries
/** /**
* The top level destinations that have unread news resources. * The top level destinations that have unread news resources.
*/ */
@ -115,6 +124,8 @@ class NiaAppState(
/** /**
* Stores information about navigation events to be used with JankStats * Stores information about navigation events to be used with JankStats
*/ */
// TODO: This shouldn't be commented out
@Composable @Composable
private fun NavigationTrackingSideEffect(niaNavigator: NiaNavigator) { private fun NavigationTrackingSideEffect(niaNavigator: NiaNavigator) {
// TrackDisposableJank(niaNavigator) { metricsHolder -> // TrackDisposableJank(niaNavigator) { metricsHolder ->

@ -25,6 +25,7 @@ import com.google.samples.apps.nowinandroid.core.testing.util.TestNetworkMonitor
import com.google.samples.apps.nowinandroid.core.testing.util.TestTimeZoneMonitor import com.google.samples.apps.nowinandroid.core.testing.util.TestTimeZoneMonitor
import com.google.samples.apps.nowinandroid.feature.bookmarks.api.navigation.BookmarksRoute 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.foryou.api.navigation.ForYouRoute
import com.google.samples.apps.nowinandroid.navigation.TOP_LEVEL_ROUTES
import dagger.hilt.android.testing.HiltAndroidTest import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.HiltTestApplication import dagger.hilt.android.testing.HiltTestApplication
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
@ -100,10 +101,10 @@ class NiaAppStateTest {
) )
} }
assertEquals(3, state.topLevelDestinations.size) assertEquals(3, TOP_LEVEL_ROUTES.size)
assertTrue(state.topLevelDestinations[0].name.contains("for_you", true)) assertTrue(TOP_LEVEL_ROUTES[0].name.contains("for_you", true))
assertTrue(state.topLevelDestinations[1].name.contains("bookmarks", true)) assertTrue(TOP_LEVEL_ROUTES[1].name.contains("bookmarks", true))
assertTrue(state.topLevelDestinations[2].name.contains("interests", true)) assertTrue(TOP_LEVEL_ROUTES[2].name.contains("interests", true))
} }
@Test @Test

@ -48,7 +48,7 @@ class NiaBackStackViewModelTest {
private fun createViewModel() = NiaBackStackViewModel( private fun createViewModel() = NiaBackStackViewModel(
savedStateHandle = SavedStateHandle(), savedStateHandle = SavedStateHandle(),
navigationState = NavigationState(TestStartKey), niaNavigationState = NiaNavigationState(TestStartKey),
serializersModules = serializersModules, serializersModules = serializersModules,
) )
@ -67,7 +67,7 @@ class NiaBackStackViewModelTest {
fun testNonTopLevelKeySaved() { fun testNonTopLevelKeySaved() {
val viewModel = createViewModel() val viewModel = createViewModel()
rule.setContent { rule.setContent {
val navigator = remember { NiaNavigator(viewModel.navigationState) } val navigator = remember { NiaNavigator(viewModel.niaNavigationState) }
navigator.navigate(TestKeyFirst) navigator.navigate(TestKeyFirst)
} }
assertThat(viewModel.backStackMap.size).isEqualTo(1) assertThat(viewModel.backStackMap.size).isEqualTo(1)
@ -80,7 +80,7 @@ class NiaBackStackViewModelTest {
fun testTopLevelKeySaved() { fun testTopLevelKeySaved() {
val viewModel = createViewModel() val viewModel = createViewModel()
rule.setContent { rule.setContent {
val navigator = remember { NiaNavigator(viewModel.navigationState) } val navigator = remember { NiaNavigator(viewModel.niaNavigationState) }
navigator.navigate(TestKeyFirst) navigator.navigate(TestKeyFirst)
navigator.navigate(TestTopLevelKeyFirst) navigator.navigate(TestTopLevelKeyFirst)
@ -101,7 +101,7 @@ class NiaBackStackViewModelTest {
fun testMultiStacksSaved() { fun testMultiStacksSaved() {
val viewModel = createViewModel() val viewModel = createViewModel()
rule.setContent { rule.setContent {
val navigator = remember { NiaNavigator(viewModel.navigationState) } val navigator = remember { NiaNavigator(viewModel.niaNavigationState) }
navigator.navigate(TestKeyFirst) navigator.navigate(TestKeyFirst)
navigator.navigate(TestTopLevelKeyFirst) navigator.navigate(TestTopLevelKeyFirst)
navigator.navigate(TestKeySecond) navigator.navigate(TestKeySecond)
@ -122,7 +122,7 @@ class NiaBackStackViewModelTest {
fun testPopSaved() { fun testPopSaved() {
val viewModel = createViewModel() val viewModel = createViewModel()
rule.setContent { rule.setContent {
val navigator = remember { NiaNavigator(viewModel.navigationState) } val navigator = remember { NiaNavigator(viewModel.niaNavigationState) }
navigator.navigate(TestKeyFirst) navigator.navigate(TestKeyFirst)
@ -144,14 +144,14 @@ class NiaBackStackViewModelTest {
fun testRestore() { fun testRestore() {
lateinit var scenario: ViewModelScenario<NiaBackStackViewModel> lateinit var scenario: ViewModelScenario<NiaBackStackViewModel>
lateinit var navigator: NiaNavigator lateinit var navigator: NiaNavigator
lateinit var navigatorState: NavigationState lateinit var navigatorState: NiaNavigationState
rule.setContent { rule.setContent {
navigatorState = remember { NavigationState(TestStartKey) } navigatorState = remember { NiaNavigationState(TestStartKey) }
navigator = remember { NiaNavigator(navigatorState) } navigator = remember { NiaNavigator(navigatorState) }
scenario = viewModelScenario { scenario = viewModelScenario {
NiaBackStackViewModel( NiaBackStackViewModel(
savedStateHandle = createSavedStateHandle(), savedStateHandle = createSavedStateHandle(),
navigationState = navigatorState, niaNavigationState = navigatorState,
serializersModules = serializersModules, serializersModules = serializersModules,
) )
} }
@ -181,14 +181,14 @@ class NiaBackStackViewModelTest {
fun testRestoreMultiStacks() { fun testRestoreMultiStacks() {
lateinit var scenario: ViewModelScenario<NiaBackStackViewModel> lateinit var scenario: ViewModelScenario<NiaBackStackViewModel>
lateinit var navigator: NiaNavigator lateinit var navigator: NiaNavigator
lateinit var navigatorState: NavigationState lateinit var navigatorState: NiaNavigationState
rule.setContent { rule.setContent {
navigatorState = remember { NavigationState(TestStartKey) } navigatorState = remember { NiaNavigationState(TestStartKey) }
navigator = remember { NiaNavigator(navigatorState) } navigator = remember { NiaNavigator(navigatorState) }
scenario = viewModelScenario { scenario = viewModelScenario {
NiaBackStackViewModel( NiaBackStackViewModel(
savedStateHandle = createSavedStateHandle(), savedStateHandle = createSavedStateHandle(),
navigationState = navigatorState, niaNavigationState = navigatorState,
serializersModules = serializersModules, serializersModules = serializersModules,
) )
} }

@ -39,10 +39,11 @@ import javax.inject.Inject
* https://github.com/android/nav3-recipes/blob/main/app/src/main/java/com/example/nav3recipes/multiplestacks/NavigationState.kt#L71 * https://github.com/android/nav3-recipes/blob/main/app/src/main/java/com/example/nav3recipes/multiplestacks/NavigationState.kt#L71
* *
*/ */
/*
@HiltViewModel @HiltViewModel
class NiaBackStackViewModel @Inject constructor( class NiaBackStackViewModel @Inject constructor(
savedStateHandle: SavedStateHandle, savedStateHandle: SavedStateHandle,
val navigationState: NavigationState, val niaNavigationState: NiaNavigationState,
serializersModules: SerializersModule, serializersModules: SerializersModule,
) : ViewModel() { ) : ViewModel() {
@ -71,7 +72,7 @@ class NiaBackStackViewModel @Inject constructor(
if (backStackMap.isNotEmpty()) { if (backStackMap.isNotEmpty()) {
// Restore backstack from saved state handle if not empty // Restore backstack from saved state handle if not empty
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
navigationState.restore( niaNavigationState.restore(
activeTopLeveLKeys, activeTopLeveLKeys,
backStackMap as LinkedHashMap<NiaNavKey, SnapshotStateList<NiaNavKey>>, backStackMap as LinkedHashMap<NiaNavKey, SnapshotStateList<NiaNavKey>>,
) )
@ -80,9 +81,10 @@ class NiaBackStackViewModel @Inject constructor(
// Start observing changes to the backStack and save backStack whenever it updates // Start observing changes to the backStack and save backStack whenever it updates
viewModelScope.launch { viewModelScope.launch {
snapshotFlow { snapshotFlow {
activeTopLeveLKeys = navigationState.activeTopLeveLKeys.toList() activeTopLeveLKeys = niaNavigationState.activeTopLeveLKeys.toList()
backStackMap = navigationState.backStacks backStackMap = niaNavigationState.backStacks
}.collect() }.collect()
} }
} }
} }
*/

@ -35,7 +35,7 @@ import javax.inject.Inject
import kotlin.collections.plus import kotlin.collections.plus
// TODO: Consider changing this to `NiaNavigationState` // TODO: Consider changing this to `NiaNavigationState`
class NavigationState( class NiaNavigationState(
internal val startKey: NiaNavKey, internal val startKey: NiaNavKey,
) { ) {
internal var backStacks: MutableMap<NiaNavKey, SnapshotStateList<NiaNavKey>> = internal var backStacks: MutableMap<NiaNavKey, SnapshotStateList<NiaNavKey>> =
@ -81,14 +81,14 @@ class NavigationState(
* TODO: Document this * TODO: Document this
*/ */
class NiaNavigator @Inject constructor( class NiaNavigator @Inject constructor(
val navigationState: NavigationState, val niaNavigationState: NiaNavigationState,
) { ) {
// TODO: I wonder if it'd be simpler to have separate methods // 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 // for navigating to a graph and navigating to a key. If the key is on a separate graph then
// navigate to that graph first. // navigate to that graph first.
fun navigate(key: NiaNavKey) { fun navigate(key: NiaNavKey) {
val currentActiveSubStacks = linkedSetOf<NiaNavKey>() val currentActiveSubStacks = linkedSetOf<NiaNavKey>()
navigationState.apply { niaNavigationState.apply {
currentActiveSubStacks.addAll(activeTopLeveLKeys) currentActiveSubStacks.addAll(activeTopLeveLKeys)
when { when {
// top level singleTop -> clear substack // top level singleTop -> clear substack
@ -126,7 +126,7 @@ class NiaNavigator @Inject constructor(
} }
fun pop() { fun pop() {
navigationState.apply { niaNavigationState.apply {
val currentSubstack = backStacks[currentTopLevelKey]!! val currentSubstack = backStacks[currentTopLevelKey]!!
if (currentSubstack.size == 1) { if (currentSubstack.size == 1) {
// if current sub-stack only has one key, remove the sub-stack from the map // if current sub-stack only has one key, remove the sub-stack from the map
@ -149,7 +149,7 @@ interface NiaNavKey {
* Convert the navigation state to `NavEntry`s that can be displayed by a `NavDisplay` * Convert the navigation state to `NavEntry`s that can be displayed by a `NavDisplay`
*/ */
@Composable @Composable
fun NavigationState.toEntries( fun NiaNavigationState.toEntries(
// TODO: Might be better to pass this in fully constructed // TODO: Might be better to pass this in fully constructed
entryProviderBuilders: Set<EntryProviderScope<NiaNavKey>.() -> Unit>, entryProviderBuilders: Set<EntryProviderScope<NiaNavKey>.() -> Unit>,
): List<NavEntry<NiaNavKey>> = ): List<NavEntry<NiaNavKey>> =

@ -0,0 +1,108 @@
/*
* 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.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSerializable
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.runtime.toMutableStateList
import androidx.navigation3.runtime.NavBackStack
import androidx.navigation3.runtime.NavEntry
import androidx.navigation3.runtime.NavKey
import androidx.navigation3.runtime.rememberDecoratedNavEntries
import androidx.navigation3.runtime.rememberNavBackStack
import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator
import androidx.navigation3.runtime.serialization.NavKeySerializer
import androidx.savedstate.compose.serialization.serializers.MutableStateSerializer
/**
* Create a navigation state that persists config changes and process death.
*/
@Composable
fun rememberNavigationState(
startRoute: NavKey,
topLevelRoutes: Set<NavKey>
): NavigationState {
val topLevelRoute = rememberSerializable(
startRoute, topLevelRoutes,
serializer = MutableStateSerializer(NavKeySerializer())
) {
mutableStateOf(startRoute)
}
val backStacks = topLevelRoutes.associateWith { key -> rememberNavBackStack(key) }
return remember(startRoute, topLevelRoutes) {
NavigationState(
startRoute = startRoute,
topLevelRoute = topLevelRoute,
backStacks = backStacks
)
}
}
/**
* State holder for navigation state.
*
* @param startRoute - the start route. The user will exit the app through this route.
* @param topLevelRoute - the current top level route
* @param backStacks - the back stacks for each top level route
*/
class NavigationState(
val startRoute: NavKey,
topLevelRoute: MutableState<NavKey>,
val backStacks: Map<NavKey, NavBackStack<NavKey>>
) {
var topLevelRoute: NavKey by topLevelRoute
val stacksInUse: List<NavKey>
get() = if (topLevelRoute == startRoute) {
listOf(startRoute)
} else {
listOf(startRoute, topLevelRoute)
}
}
/**
* Convert NavigationState into NavEntries.
*/
@Composable
fun NavigationState.toEntries(
entryProvider: (NavKey) -> NavEntry<NavKey>
): SnapshotStateList<NavEntry<NavKey>> {
val decoratedEntries = backStacks.mapValues { (_, stack) ->
val decorators = listOf(
rememberSaveableStateHolderNavEntryDecorator<NavKey>(),
)
rememberDecoratedNavEntries(
backStack = stack,
entryDecorators = decorators,
entryProvider = entryProvider
)
}
return stacksInUse
.flatMap { decoratedEntries[it] ?: emptyList() }
.toMutableStateList()
}

@ -0,0 +1,47 @@
/*
* 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.example.nav3recipes.multiplestacks
import androidx.navigation3.runtime.NavKey
import com.google.samples.apps.nowinandroid.core.navigation.simple.NavigationState
/**
* 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()
}
}
}

@ -24,49 +24,49 @@ import kotlin.test.assertFailsWith
class NiaNavigatorStateTest { class NiaNavigatorStateTest {
private lateinit var navigationState: NavigationState private lateinit var niaNavigationState: NiaNavigationState
private lateinit var niaNavigator: NiaNavigator private lateinit var niaNavigator: NiaNavigator
@Before @Before
fun setup() { fun setup() {
navigationState = NavigationState(TestStartKey) niaNavigationState = NiaNavigationState(TestStartKey)
niaNavigator = NiaNavigator(navigationState) niaNavigator = NiaNavigator(niaNavigationState)
} }
@Test @Test
fun testStartKey() { fun testStartKey() {
assertThat(navigationState.currentKey).isEqualTo(TestStartKey) assertThat(niaNavigationState.currentKey).isEqualTo(TestStartKey)
assertThat(navigationState.currentTopLevelKey).isEqualTo(TestStartKey) assertThat(niaNavigationState.currentTopLevelKey).isEqualTo(TestStartKey)
} }
@Test @Test
fun testNavigate() { fun testNavigate() {
niaNavigator.navigate(TestKeyFirst) niaNavigator.navigate(TestKeyFirst)
assertThat(navigationState.currentKey).isEqualTo(TestKeyFirst) assertThat(niaNavigationState.currentKey).isEqualTo(TestKeyFirst)
assertThat(navigationState.currentTopLevelKey).isEqualTo(TestStartKey) assertThat(niaNavigationState.currentTopLevelKey).isEqualTo(TestStartKey)
} }
@Test @Test
fun testNavigateTopLevel() { fun testNavigateTopLevel() {
niaNavigator.navigate(TestTopLevelKey) niaNavigator.navigate(TestTopLevelKey)
assertThat(navigationState.currentKey).isEqualTo(TestTopLevelKey) assertThat(niaNavigationState.currentKey).isEqualTo(TestTopLevelKey)
assertThat(navigationState.currentTopLevelKey).isEqualTo(TestTopLevelKey) assertThat(niaNavigationState.currentTopLevelKey).isEqualTo(TestTopLevelKey)
} }
@Test @Test
fun testNavigateSingleTop() { fun testNavigateSingleTop() {
niaNavigator.navigate(TestKeyFirst) niaNavigator.navigate(TestKeyFirst)
assertThat(navigationState.currentBackStack).containsExactly( assertThat(niaNavigationState.currentBackStack).containsExactly(
TestStartKey, TestStartKey,
TestKeyFirst, TestKeyFirst,
).inOrder() ).inOrder()
niaNavigator.navigate(TestKeyFirst) niaNavigator.navigate(TestKeyFirst)
assertThat(navigationState.currentBackStack).containsExactly( assertThat(niaNavigationState.currentBackStack).containsExactly(
TestStartKey, TestStartKey,
TestKeyFirst, TestKeyFirst,
).inOrder() ).inOrder()
@ -77,7 +77,7 @@ class NiaNavigatorStateTest {
niaNavigator.navigate(TestTopLevelKey) niaNavigator.navigate(TestTopLevelKey)
niaNavigator.navigate(TestKeyFirst) niaNavigator.navigate(TestKeyFirst)
assertThat(navigationState.currentBackStack).containsExactly( assertThat(niaNavigationState.currentBackStack).containsExactly(
TestStartKey, TestStartKey,
TestTopLevelKey, TestTopLevelKey,
TestKeyFirst, TestKeyFirst,
@ -85,7 +85,7 @@ class NiaNavigatorStateTest {
niaNavigator.navigate(TestTopLevelKey) niaNavigator.navigate(TestTopLevelKey)
assertThat(navigationState.currentBackStack).containsExactly( assertThat(niaNavigationState.currentBackStack).containsExactly(
TestStartKey, TestStartKey,
TestTopLevelKey, TestTopLevelKey,
).inOrder() ).inOrder()
@ -95,13 +95,13 @@ class NiaNavigatorStateTest {
fun testSubStack() { fun testSubStack() {
niaNavigator.navigate(TestKeyFirst) niaNavigator.navigate(TestKeyFirst)
assertThat(navigationState.currentKey).isEqualTo(TestKeyFirst) assertThat(niaNavigationState.currentKey).isEqualTo(TestKeyFirst)
assertThat(navigationState.currentTopLevelKey).isEqualTo(TestStartKey) assertThat(niaNavigationState.currentTopLevelKey).isEqualTo(TestStartKey)
niaNavigator.navigate(TestKeySecond) niaNavigator.navigate(TestKeySecond)
assertThat(navigationState.currentKey).isEqualTo(TestKeySecond) assertThat(niaNavigationState.currentKey).isEqualTo(TestKeySecond)
assertThat(navigationState.currentTopLevelKey).isEqualTo(TestStartKey) assertThat(niaNavigationState.currentTopLevelKey).isEqualTo(TestStartKey)
} }
@Test @Test
@ -109,33 +109,33 @@ class NiaNavigatorStateTest {
// add to start stack // add to start stack
niaNavigator.navigate(TestKeyFirst) niaNavigator.navigate(TestKeyFirst)
assertThat(navigationState.currentKey).isEqualTo(TestKeyFirst) assertThat(niaNavigationState.currentKey).isEqualTo(TestKeyFirst)
assertThat(navigationState.currentTopLevelKey).isEqualTo(TestStartKey) assertThat(niaNavigationState.currentTopLevelKey).isEqualTo(TestStartKey)
// navigate to new top level // navigate to new top level
niaNavigator.navigate(TestTopLevelKey) niaNavigator.navigate(TestTopLevelKey)
assertThat(navigationState.currentKey).isEqualTo(TestTopLevelKey) assertThat(niaNavigationState.currentKey).isEqualTo(TestTopLevelKey)
assertThat(navigationState.currentTopLevelKey).isEqualTo(TestTopLevelKey) assertThat(niaNavigationState.currentTopLevelKey).isEqualTo(TestTopLevelKey)
// add to new stack // add to new stack
niaNavigator.navigate(TestKeySecond) niaNavigator.navigate(TestKeySecond)
assertThat(navigationState.currentKey).isEqualTo(TestKeySecond) assertThat(niaNavigationState.currentKey).isEqualTo(TestKeySecond)
assertThat(navigationState.currentTopLevelKey).isEqualTo(TestTopLevelKey) assertThat(niaNavigationState.currentTopLevelKey).isEqualTo(TestTopLevelKey)
// go back to start stack // go back to start stack
niaNavigator.navigate(TestStartKey) niaNavigator.navigate(TestStartKey)
assertThat(navigationState.currentKey).isEqualTo(TestKeyFirst) assertThat(niaNavigationState.currentKey).isEqualTo(TestKeyFirst)
assertThat(navigationState.currentTopLevelKey).isEqualTo(TestStartKey) assertThat(niaNavigationState.currentTopLevelKey).isEqualTo(TestStartKey)
} }
@Test @Test
fun testRestore() { fun testRestore() {
assertThat(navigationState.currentBackStack).containsExactly(TestStartKey) assertThat(niaNavigationState.currentBackStack).containsExactly(TestStartKey)
navigationState.restore( niaNavigationState.restore(
listOf(TestStartKey, TestTopLevelKey), listOf(TestStartKey, TestTopLevelKey),
linkedMapOf( linkedMapOf(
TestStartKey to mutableStateListOf(TestStartKey, TestKeyFirst), TestStartKey to mutableStateListOf(TestStartKey, TestKeyFirst),
@ -143,15 +143,15 @@ class NiaNavigatorStateTest {
), ),
) )
assertThat(navigationState.currentBackStack).containsExactly( assertThat(niaNavigationState.currentBackStack).containsExactly(
TestStartKey, TestStartKey,
TestKeyFirst, TestKeyFirst,
TestTopLevelKey, TestTopLevelKey,
TestKeySecond, TestKeySecond,
).inOrder() ).inOrder()
assertThat(navigationState.currentKey).isEqualTo(TestKeySecond) assertThat(niaNavigationState.currentKey).isEqualTo(TestKeySecond)
assertThat(navigationState.currentTopLevelKey).isEqualTo(TestTopLevelKey) assertThat(niaNavigationState.currentTopLevelKey).isEqualTo(TestTopLevelKey)
} }
@Test @Test
@ -159,7 +159,7 @@ class NiaNavigatorStateTest {
niaNavigator.navigate(TestKeyFirst) niaNavigator.navigate(TestKeyFirst)
niaNavigator.navigate(TestKeySecond) niaNavigator.navigate(TestKeySecond)
assertThat(navigationState.currentBackStack).containsExactly( assertThat(niaNavigationState.currentBackStack).containsExactly(
TestStartKey, TestStartKey,
TestKeyFirst, TestKeyFirst,
TestKeySecond, TestKeySecond,
@ -167,13 +167,13 @@ class NiaNavigatorStateTest {
niaNavigator.pop() niaNavigator.pop()
assertThat(navigationState.currentBackStack).containsExactly( assertThat(niaNavigationState.currentBackStack).containsExactly(
TestStartKey, TestStartKey,
TestKeyFirst, TestKeyFirst,
).inOrder() ).inOrder()
assertThat(navigationState.currentKey).isEqualTo(TestKeyFirst) assertThat(niaNavigationState.currentKey).isEqualTo(TestKeyFirst)
assertThat(navigationState.currentTopLevelKey).isEqualTo(TestStartKey) assertThat(niaNavigationState.currentTopLevelKey).isEqualTo(TestStartKey)
} }
@Test @Test
@ -181,25 +181,25 @@ class NiaNavigatorStateTest {
niaNavigator.navigate(TestKeyFirst) niaNavigator.navigate(TestKeyFirst)
niaNavigator.navigate(TestTopLevelKey) niaNavigator.navigate(TestTopLevelKey)
assertThat(navigationState.currentBackStack).containsExactly( assertThat(niaNavigationState.currentBackStack).containsExactly(
TestStartKey, TestStartKey,
TestKeyFirst, TestKeyFirst,
TestTopLevelKey, TestTopLevelKey,
).inOrder() ).inOrder()
assertThat(navigationState.currentKey).isEqualTo(TestTopLevelKey) assertThat(niaNavigationState.currentKey).isEqualTo(TestTopLevelKey)
assertThat(navigationState.currentTopLevelKey).isEqualTo(TestTopLevelKey) assertThat(niaNavigationState.currentTopLevelKey).isEqualTo(TestTopLevelKey)
// remove TopLevel // remove TopLevel
niaNavigator.pop() niaNavigator.pop()
assertThat(navigationState.currentBackStack).containsExactly( assertThat(niaNavigationState.currentBackStack).containsExactly(
TestStartKey, TestStartKey,
TestKeyFirst, TestKeyFirst,
).inOrder() ).inOrder()
assertThat(navigationState.currentKey).isEqualTo(TestKeyFirst) assertThat(niaNavigationState.currentKey).isEqualTo(TestKeyFirst)
assertThat(navigationState.currentTopLevelKey).isEqualTo(TestStartKey) assertThat(niaNavigationState.currentTopLevelKey).isEqualTo(TestStartKey)
} }
@Test @Test
@ -207,7 +207,7 @@ class NiaNavigatorStateTest {
niaNavigator.navigate(TestKeyFirst) niaNavigator.navigate(TestKeyFirst)
niaNavigator.navigate(TestKeySecond) niaNavigator.navigate(TestKeySecond)
assertThat(navigationState.currentBackStack).containsExactly( assertThat(niaNavigationState.currentBackStack).containsExactly(
TestStartKey, TestStartKey,
TestKeyFirst, TestKeyFirst,
TestKeySecond, TestKeySecond,
@ -216,12 +216,12 @@ class NiaNavigatorStateTest {
niaNavigator.pop() niaNavigator.pop()
niaNavigator.pop() niaNavigator.pop()
assertThat(navigationState.currentBackStack).containsExactly( assertThat(niaNavigationState.currentBackStack).containsExactly(
TestStartKey, TestStartKey,
).inOrder() ).inOrder()
assertThat(navigationState.currentKey).isEqualTo(TestStartKey) assertThat(niaNavigationState.currentKey).isEqualTo(TestStartKey)
assertThat(navigationState.currentTopLevelKey).isEqualTo(TestStartKey) assertThat(niaNavigationState.currentTopLevelKey).isEqualTo(TestStartKey)
} }
@Test @Test
@ -238,7 +238,7 @@ class NiaNavigatorStateTest {
niaNavigator.navigate(testTopLevelKeyTwo) niaNavigator.navigate(testTopLevelKeyTwo)
niaNavigator.navigate(TestKeySecond) niaNavigator.navigate(TestKeySecond)
assertThat(navigationState.currentBackStack).containsExactly( assertThat(niaNavigationState.currentBackStack).containsExactly(
TestStartKey, TestStartKey,
TestTopLevelKey, TestTopLevelKey,
TestKeyFirst, TestKeyFirst,
@ -250,12 +250,12 @@ class NiaNavigatorStateTest {
niaNavigator.pop() niaNavigator.pop()
} }
assertThat(navigationState.currentBackStack).containsExactly( assertThat(niaNavigationState.currentBackStack).containsExactly(
TestStartKey, TestStartKey,
).inOrder() ).inOrder()
assertThat(navigationState.currentKey).isEqualTo(TestStartKey) assertThat(niaNavigationState.currentKey).isEqualTo(TestStartKey)
assertThat(navigationState.currentTopLevelKey).isEqualTo(TestStartKey) assertThat(niaNavigationState.currentTopLevelKey).isEqualTo(TestStartKey)
} }
@Test @Test

@ -16,11 +16,15 @@
package com.google.samples.apps.nowinandroid.feature.bookmarks.api.navigation package com.google.samples.apps.nowinandroid.feature.bookmarks.api.navigation
import androidx.navigation3.runtime.NavKey
import com.google.samples.apps.nowinandroid.core.navigation.NiaNavKey import com.google.samples.apps.nowinandroid.core.navigation.NiaNavKey
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@Serializable /*@Serializable
object BookmarksRoute : NiaNavKey { object BookmarksRoute : NiaNavKey {
override val isTopLevel: Boolean override val isTopLevel: Boolean
get() = true get() = true
} }*/
@Serializable
object BookmarksRoute : NavKey

@ -21,11 +21,14 @@ import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult.ActionPerformed import androidx.compose.material3.SnackbarResult.ActionPerformed
import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.compositionLocalOf
import androidx.navigation3.runtime.EntryProviderScope import androidx.navigation3.runtime.EntryProviderScope
import androidx.navigation3.runtime.NavKey
import com.example.nav3recipes.multiplestacks.Navigator
import com.google.samples.apps.nowinandroid.core.navigation.NiaNavKey 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.NiaNavigator
import com.google.samples.apps.nowinandroid.feature.bookmarks.api.navigation.BookmarksRoute import com.google.samples.apps.nowinandroid.feature.bookmarks.api.navigation.BookmarksRoute
import com.google.samples.apps.nowinandroid.feature.bookmarks.impl.BookmarksScreen import com.google.samples.apps.nowinandroid.feature.bookmarks.impl.BookmarksScreen
import com.google.samples.apps.nowinandroid.feature.topic.api.navigation.navigateToTopic import com.google.samples.apps.nowinandroid.feature.topic.api.navigation.TopicRoute
//import com.google.samples.apps.nowinandroid.feature.topic.api.navigation.navigateToTopic
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
@ -39,12 +42,15 @@ object BookmarksEntryProvider {
@Provides @Provides
@IntoSet @IntoSet
fun provideBookmarksEntryProviderBuilder( fun provideBookmarksEntryProviderBuilder(
navigator: NiaNavigator, navigator: Navigator,
): EntryProviderScope<NiaNavKey>.() -> Unit = { ): EntryProviderScope<NavKey>.() -> Unit = { bookmarksEntry(navigator) }
}
fun EntryProviderScope<NavKey>.bookmarksEntry(navigator: Navigator) {
entry<BookmarksRoute> { entry<BookmarksRoute> {
val snackbarHostState = LocalSnackbarHostState.current val snackbarHostState = LocalSnackbarHostState.current
BookmarksScreen( BookmarksScreen(
onTopicClick = navigator::navigateToTopic, onTopicClick = { topicId -> navigator.navigate(TopicRoute(topicId)) },
onShowSnackbar = { message, action -> onShowSnackbar = { message, action ->
snackbarHostState.showSnackbar( snackbarHostState.showSnackbar(
message = message, message = message,
@ -54,7 +60,6 @@ object BookmarksEntryProvider {
}, },
) )
} }
}
} }
val LocalSnackbarHostState = compositionLocalOf<SnackbarHostState> { val LocalSnackbarHostState = compositionLocalOf<SnackbarHostState> {

@ -29,6 +29,7 @@ import kotlinx.serialization.modules.PolymorphicModuleBuilder
* Provides the DSL to register the route's [kotlinx.serialization.KSerializer] as a polymorphic serializer * Provides the DSL to register the route's [kotlinx.serialization.KSerializer] as a polymorphic serializer
* *
*/ */
/*
@Module @Module
@InstallIn(SingletonComponent::class) @InstallIn(SingletonComponent::class)
object BookmarksSerializerModule { object BookmarksSerializerModule {
@ -38,3 +39,4 @@ object BookmarksSerializerModule {
subclass(BookmarksRoute::class, BookmarksRoute.serializer()) subclass(BookmarksRoute::class, BookmarksRoute.serializer())
} }
} }
*/

@ -16,11 +16,17 @@
package com.google.samples.apps.nowinandroid.feature.foryou.api.navigation package com.google.samples.apps.nowinandroid.feature.foryou.api.navigation
import androidx.navigation3.runtime.NavKey
import com.google.samples.apps.nowinandroid.core.navigation.NiaNavKey import com.google.samples.apps.nowinandroid.core.navigation.NiaNavKey
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
/*
@Serializable @Serializable
object ForYouRoute : NiaNavKey { // route to ForYou screen object ForYouRoute : NiaNavKey { // route to ForYou screen
override val isTopLevel: Boolean override val isTopLevel: Boolean
get() = true get() = true
} }
*/
@Serializable
object ForYouRoute : NavKey

@ -17,11 +17,14 @@
package com.google.samples.apps.nowinandroid.feature.foryou.impl.navigation package com.google.samples.apps.nowinandroid.feature.foryou.impl.navigation
import androidx.navigation3.runtime.EntryProviderScope import androidx.navigation3.runtime.EntryProviderScope
import androidx.navigation3.runtime.NavKey
import com.example.nav3recipes.multiplestacks.Navigator
import com.google.samples.apps.nowinandroid.core.navigation.NiaNavKey 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.NiaNavigator
import com.google.samples.apps.nowinandroid.feature.foryou.api.navigation.ForYouRoute import com.google.samples.apps.nowinandroid.feature.foryou.api.navigation.ForYouRoute
import com.google.samples.apps.nowinandroid.feature.foryou.impl.ForYouScreen import com.google.samples.apps.nowinandroid.feature.foryou.impl.ForYouScreen
import com.google.samples.apps.nowinandroid.feature.topic.api.navigation.navigateToTopic import com.google.samples.apps.nowinandroid.feature.topic.api.navigation.TopicRoute
//import com.google.samples.apps.nowinandroid.feature.topic.api.navigation.navigateToTopic
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
@ -38,14 +41,14 @@ object ForYouEntryProvider {
@Provides @Provides
@IntoSet @IntoSet
fun provideForYouEntryProviderBuilder( fun provideForYouEntryProviderBuilder(
navigator: NiaNavigator, navigator: Navigator,
): EntryProviderScope<NiaNavKey>.() -> Unit = forYouEntry(navigator) ): EntryProviderScope<NavKey>.() -> Unit = { forYouEntry(navigator) }
} }
fun forYouEntry(navigator: NiaNavigator): EntryProviderScope<NiaNavKey>.() -> Unit = { fun EntryProviderScope<NavKey>.forYouEntry(navigator: Navigator) {
entry<ForYouRoute> { entry<ForYouRoute> {
ForYouScreen( ForYouScreen(
onTopicClick = navigator::navigateToTopic, onTopicClick = { topicId -> navigator.navigate(TopicRoute(topicId)) },
) )
} }
} }

@ -29,6 +29,7 @@ import kotlinx.serialization.modules.PolymorphicModuleBuilder
* Provides the DSL to register the route's [kotlinx.serialization.KSerializer] as a polymorphic serializer * Provides the DSL to register the route's [kotlinx.serialization.KSerializer] as a polymorphic serializer
* *
*/ */
/*
@Module @Module
@InstallIn(SingletonComponent::class) @InstallIn(SingletonComponent::class)
object ForYouRouteSerializerModule { object ForYouRouteSerializerModule {
@ -38,3 +39,4 @@ object ForYouRouteSerializerModule {
subclass(ForYouRoute::class, ForYouRoute.serializer()) subclass(ForYouRoute::class, ForYouRoute.serializer())
} }
} }
*/

@ -16,9 +16,11 @@
package com.google.samples.apps.nowinandroid.feature.interests.api.navigation package com.google.samples.apps.nowinandroid.feature.interests.api.navigation
import androidx.navigation3.runtime.NavKey
import com.google.samples.apps.nowinandroid.core.navigation.NiaNavKey import com.google.samples.apps.nowinandroid.core.navigation.NiaNavKey
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
/*
@Serializable @Serializable
data class InterestsRoute( data class InterestsRoute(
// The ID of the topic which will be initially selected at this destination // The ID of the topic which will be initially selected at this destination
@ -27,3 +29,11 @@ data class InterestsRoute(
override val isTopLevel: Boolean override val isTopLevel: Boolean
get() = true get() = true
} }
*/
@Serializable
data class InterestsRoute(
// The ID of the topic which will be initially selected at this destination
val initialTopicId: String? = null,
) : NavKey

@ -20,13 +20,16 @@ import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
import androidx.compose.material3.adaptive.navigation3.ListDetailSceneStrategy import androidx.compose.material3.adaptive.navigation3.ListDetailSceneStrategy
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.navigation3.runtime.EntryProviderScope import androidx.navigation3.runtime.EntryProviderScope
import androidx.navigation3.runtime.NavKey
import com.example.nav3recipes.multiplestacks.Navigator
import com.google.samples.apps.nowinandroid.core.navigation.NiaNavKey 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.NiaNavigator
import com.google.samples.apps.nowinandroid.feature.interests.api.navigation.InterestsRoute import com.google.samples.apps.nowinandroid.feature.interests.api.navigation.InterestsRoute
import com.google.samples.apps.nowinandroid.feature.interests.impl.InterestsDetailPlaceholder import com.google.samples.apps.nowinandroid.feature.interests.impl.InterestsDetailPlaceholder
import com.google.samples.apps.nowinandroid.feature.interests.impl.InterestsScreen import com.google.samples.apps.nowinandroid.feature.interests.impl.InterestsScreen
import com.google.samples.apps.nowinandroid.feature.interests.impl.InterestsViewModel import com.google.samples.apps.nowinandroid.feature.interests.impl.InterestsViewModel
import com.google.samples.apps.nowinandroid.feature.topic.api.navigation.navigateToTopic import com.google.samples.apps.nowinandroid.feature.topic.api.navigation.TopicRoute
//import com.google.samples.apps.nowinandroid.feature.topic.api.navigation.navigateToTopic
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
@ -41,8 +44,12 @@ object InterestsEntryProvider {
@Provides @Provides
@IntoSet @IntoSet
fun provideInterestsEntryProviderBuilder( fun provideInterestsEntryProviderBuilder(
navigator: NiaNavigator, navigator: Navigator,
): EntryProviderScope<NiaNavKey>.() -> Unit = { ): EntryProviderScope<NavKey>.() -> Unit = { interestsEntry(navigator) }
}
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
fun EntryProviderScope<NavKey>.interestsEntry(navigator: Navigator) {
entry<InterestsRoute>( entry<InterestsRoute>(
metadata = ListDetailSceneStrategy.listPane { metadata = ListDetailSceneStrategy.listPane {
InterestsDetailPlaceholder() InterestsDetailPlaceholder()
@ -53,7 +60,8 @@ object InterestsEntryProvider {
} }
InterestsScreen( InterestsScreen(
// TODO: This event should be provided by the ViewModel // TODO: This event should be provided by the ViewModel
onTopicClick = navigator::navigateToTopic, // TODO: This could be made into an extension function on navigator
onTopicClick = { topicId -> navigator.navigate(TopicRoute(topicId)) },
// TODO: This should be dynamically calculated based on the rendering scene // TODO: This should be dynamically calculated based on the rendering scene
// See https://github.com/android/nav3-recipes/commit/488f4811791ca3ed7192f4fe3c86e7371b32ebdc#diff-374e02026cdd2f68057dd940f203dc4ba7319930b33e9555c61af7e072211cabR89 // See https://github.com/android/nav3-recipes/commit/488f4811791ca3ed7192f4fe3c86e7371b32ebdc#diff-374e02026cdd2f68057dd940f203dc4ba7319930b33e9555c61af7e072211cabR89
@ -61,5 +69,4 @@ object InterestsEntryProvider {
viewModel = viewModel, viewModel = viewModel,
) )
} }
}
} }

@ -29,6 +29,7 @@ import kotlinx.serialization.modules.PolymorphicModuleBuilder
* Provides the DSL to register the route's [kotlinx.serialization.KSerializer] as a polymorphic serializer * Provides the DSL to register the route's [kotlinx.serialization.KSerializer] as a polymorphic serializer
* *
*/ */
/*
@Module @Module
@InstallIn(SingletonComponent::class) @InstallIn(SingletonComponent::class)
object InterestsSerializerModule { object InterestsSerializerModule {
@ -38,3 +39,4 @@ object InterestsSerializerModule {
subclass(InterestsRoute::class, InterestsRoute.serializer()) subclass(InterestsRoute::class, InterestsRoute.serializer())
} }
} }
*/

@ -16,10 +16,12 @@
package com.google.samples.apps.nowinandroid.feature.search.api.navigation package com.google.samples.apps.nowinandroid.feature.search.api.navigation
import androidx.navigation3.runtime.NavKey
import com.google.samples.apps.nowinandroid.core.navigation.NiaNavKey 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.NiaNavigator
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
/*
@Serializable @Serializable
object SearchRoute : NiaNavKey { object SearchRoute : NiaNavKey {
override val isTopLevel: Boolean override val isTopLevel: Boolean
@ -29,3 +31,7 @@ object SearchRoute : NiaNavKey {
fun NiaNavigator.navigateToSearch() { fun NiaNavigator.navigateToSearch() {
navigate(SearchRoute) navigate(SearchRoute)
} }
*/
@Serializable
object SearchRoute : NavKey

@ -17,12 +17,15 @@
package com.google.samples.apps.nowinandroid.feature.search.impl.navigation package com.google.samples.apps.nowinandroid.feature.search.impl.navigation
import androidx.navigation3.runtime.EntryProviderScope import androidx.navigation3.runtime.EntryProviderScope
import androidx.navigation3.runtime.NavKey
import com.example.nav3recipes.multiplestacks.Navigator
import com.google.samples.apps.nowinandroid.core.navigation.NiaNavKey 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.NiaNavigator
import com.google.samples.apps.nowinandroid.feature.interests.api.navigation.InterestsRoute import com.google.samples.apps.nowinandroid.feature.interests.api.navigation.InterestsRoute
import com.google.samples.apps.nowinandroid.feature.search.api.navigation.SearchRoute import com.google.samples.apps.nowinandroid.feature.search.api.navigation.SearchRoute
import com.google.samples.apps.nowinandroid.feature.search.impl.SearchScreen import com.google.samples.apps.nowinandroid.feature.search.impl.SearchScreen
import com.google.samples.apps.nowinandroid.feature.topic.api.navigation.navigateToTopic import com.google.samples.apps.nowinandroid.feature.search.impl.navigation.searchEntry
import com.google.samples.apps.nowinandroid.feature.topic.api.navigation.TopicRoute
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
@ -36,14 +39,16 @@ object SearchEntryProvider {
@Provides @Provides
@IntoSet @IntoSet
fun provideSearchEntryProviderBuilder( fun provideSearchEntryProviderBuilder(
navigator: NiaNavigator, navigator: Navigator,
): EntryProviderScope<NiaNavKey>.() -> Unit = { ): EntryProviderScope<NavKey>.() -> Unit = { searchEntry(navigator) }
}
fun EntryProviderScope<NavKey>.searchEntry(navigator: Navigator) {
entry<SearchRoute> { key -> entry<SearchRoute> { key ->
SearchScreen( SearchScreen(
onBackClick = navigator::pop, onBackClick = { navigator.goBack() },
onInterestsClick = { navigator.navigate(InterestsRoute()) }, onInterestsClick = { navigator.navigate(InterestsRoute()) },
onTopicClick = navigator::navigateToTopic, onTopicClick = { topicId -> navigator.navigate(TopicRoute(topicId)) },
) )
} }
}
} }

@ -29,7 +29,7 @@ import kotlinx.serialization.modules.PolymorphicModuleBuilder
* Provides the DSL to register the route's [kotlinx.serialization.KSerializer] as a polymorphic serializer * Provides the DSL to register the route's [kotlinx.serialization.KSerializer] as a polymorphic serializer
* *
*/ */
@Module /*@Module
@InstallIn(SingletonComponent::class) @InstallIn(SingletonComponent::class)
object SearchSerializerModule { object SearchSerializerModule {
@Provides @Provides
@ -37,4 +37,4 @@ object SearchSerializerModule {
fun provideSearchPolymorphicModuleBuilder(): PolymorphicModuleBuilder<@JvmSuppressWildcards NiaNavKey>.() -> Unit = { fun provideSearchPolymorphicModuleBuilder(): PolymorphicModuleBuilder<@JvmSuppressWildcards NiaNavKey>.() -> Unit = {
subclass(SearchRoute::class, SearchRoute.serializer()) subclass(SearchRoute::class, SearchRoute.serializer())
} }
} }*/

@ -16,10 +16,12 @@
package com.google.samples.apps.nowinandroid.feature.topic.api.navigation package com.google.samples.apps.nowinandroid.feature.topic.api.navigation
import androidx.navigation3.runtime.NavKey
import com.google.samples.apps.nowinandroid.core.navigation.NiaNavKey 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.NiaNavigator
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
/*
@Serializable @Serializable
data class TopicRoute(val id: String) : NiaNavKey { data class TopicRoute(val id: String) : NiaNavKey {
override val isTopLevel: Boolean override val isTopLevel: Boolean
@ -31,3 +33,7 @@ fun NiaNavigator.navigateToTopic(
) { ) {
navigate(TopicRoute(topicId)) navigate(TopicRoute(topicId))
} }
*/
@Serializable
data class TopicRoute(val id: String) : NavKey

@ -28,7 +28,7 @@ import kotlinx.serialization.modules.PolymorphicModuleBuilder
* Provides the DSL to register the route's [kotlinx.serialization.KSerializer] as a polymorphic serializer * Provides the DSL to register the route's [kotlinx.serialization.KSerializer] as a polymorphic serializer
* *
*/ */
@Module /*@Module
@InstallIn(SingletonComponent::class) @InstallIn(SingletonComponent::class)
object TopicSerializerModule { object TopicSerializerModule {
@Provides @Provides
@ -36,4 +36,4 @@ object TopicSerializerModule {
fun provideTopicPolymorphicModuleBuilder(): PolymorphicModuleBuilder<@JvmSuppressWildcards NiaNavKey>.() -> Unit = { fun provideTopicPolymorphicModuleBuilder(): PolymorphicModuleBuilder<@JvmSuppressWildcards NiaNavKey>.() -> Unit = {
subclass(TopicRoute::class, TopicRoute.serializer()) subclass(TopicRoute::class, TopicRoute.serializer())
} }
} }*/

@ -20,10 +20,12 @@ import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
import androidx.compose.material3.adaptive.navigation3.ListDetailSceneStrategy import androidx.compose.material3.adaptive.navigation3.ListDetailSceneStrategy
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.navigation3.runtime.EntryProviderScope import androidx.navigation3.runtime.EntryProviderScope
import androidx.navigation3.runtime.NavKey
import com.example.nav3recipes.multiplestacks.Navigator
import com.google.samples.apps.nowinandroid.core.navigation.NiaNavKey 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.NiaNavigator
import com.google.samples.apps.nowinandroid.feature.topic.api.navigation.TopicRoute import com.google.samples.apps.nowinandroid.feature.topic.api.navigation.TopicRoute
import com.google.samples.apps.nowinandroid.feature.topic.api.navigation.navigateToTopic //import com.google.samples.apps.nowinandroid.feature.topic.api.navigation.navigateToTopic
import com.google.samples.apps.nowinandroid.feature.topic.impl.TopicScreen import com.google.samples.apps.nowinandroid.feature.topic.impl.TopicScreen
import com.google.samples.apps.nowinandroid.feature.topic.impl.TopicViewModel import com.google.samples.apps.nowinandroid.feature.topic.impl.TopicViewModel
import com.google.samples.apps.nowinandroid.feature.topic.impl.TopicViewModel.Factory import com.google.samples.apps.nowinandroid.feature.topic.impl.TopicViewModel.Factory
@ -41,16 +43,20 @@ object TopicEntryProvider {
@Provides @Provides
@IntoSet @IntoSet
fun provideTopicEntryProviderBuilder( fun provideTopicEntryProviderBuilder(
navigator: NiaNavigator, navigator: Navigator,
): EntryProviderScope<NiaNavKey>.() -> Unit = { ): EntryProviderScope<NavKey>.() -> Unit = { topicEntry(navigator) }
}
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
fun EntryProviderScope<NavKey>.topicEntry(navigator: Navigator) {
entry<TopicRoute>( entry<TopicRoute>(
metadata = ListDetailSceneStrategy.detailPane(), metadata = ListDetailSceneStrategy.detailPane(),
) { key -> ) { key ->
val id = key.id val id = key.id
TopicScreen( TopicScreen(
showBackButton = true, showBackButton = true,
onBackClick = navigator::pop, onBackClick = { navigator.goBack() },
onTopicClick = navigator::navigateToTopic, onTopicClick = { topicId -> navigator.navigate(TopicRoute(topicId)) },
viewModel = hiltViewModel<TopicViewModel, Factory>( viewModel = hiltViewModel<TopicViewModel, Factory>(
key = id, key = id,
) { factory -> ) { factory ->
@ -58,5 +64,4 @@ object TopicEntryProvider {
}, },
) )
} }
}
} }

Loading…
Cancel
Save