From b3e70644932dd9b8adc98d1bf79a4b8444db98e4 Mon Sep 17 00:00:00 2001 From: Rafael Costa Date: Sat, 27 Apr 2024 16:44:47 +0100 Subject: [PATCH] Fix shadowed extension functions of NavController pt2 --- compose-destinations/build.gradle.kts | 2 +- .../navigation/DestinationsNavController.kt | 35 ----- .../DestinationsNavOptionsBuilder.kt | 48 +++++++ .../navigation/NavControllerExt.kt | 62 --------- .../result/EmptyResultBackNavigator.kt | 4 +- .../result/ResultBackNavigator.kt | 29 ++-- .../result/ResultBackNavigatorImpl.kt | 20 +-- .../result/ResultCommons.kt | 3 - .../scope/DestinationScope.kt | 2 +- .../scope/DestinationScopeInternals.kt | 6 +- .../scope/NavGraphBuilderDestinationScope.kt | 2 +- .../utils/NavControllerExt.kt | 131 ++++++++++++++++++ .../utils/SpecExtensions.kt | 106 -------------- .../samples/playground/PlaygroundApp.kt | 7 +- .../commons/composables/MyDrawer.kt | 4 +- 15 files changed, 203 insertions(+), 258 deletions(-) create mode 100644 compose-destinations/src/main/java/com/ramcosta/composedestinations/navigation/DestinationsNavOptionsBuilder.kt delete mode 100644 compose-destinations/src/main/java/com/ramcosta/composedestinations/navigation/NavControllerExt.kt create mode 100644 compose-destinations/src/main/java/com/ramcosta/composedestinations/utils/NavControllerExt.kt diff --git a/compose-destinations/build.gradle.kts b/compose-destinations/build.gradle.kts index 2be6a55f..9d71d4ca 100644 --- a/compose-destinations/build.gradle.kts +++ b/compose-destinations/build.gradle.kts @@ -52,5 +52,5 @@ tasks.withType().configureEach } dependencies { - implementation(libs.compose.navigation) + api(libs.compose.navigation) } diff --git a/compose-destinations/src/main/java/com/ramcosta/composedestinations/navigation/DestinationsNavController.kt b/compose-destinations/src/main/java/com/ramcosta/composedestinations/navigation/DestinationsNavController.kt index 1a1f9312..69b0d403 100644 --- a/compose-destinations/src/main/java/com/ramcosta/composedestinations/navigation/DestinationsNavController.kt +++ b/compose-destinations/src/main/java/com/ramcosta/composedestinations/navigation/DestinationsNavController.kt @@ -3,44 +3,9 @@ package com.ramcosta.composedestinations.navigation import androidx.annotation.MainThread import androidx.navigation.NavController import androidx.navigation.NavOptions -import androidx.navigation.NavOptionsBuilder import androidx.navigation.Navigator -import androidx.navigation.PopUpToBuilder import com.ramcosta.composedestinations.spec.Direction import com.ramcosta.composedestinations.spec.Route -import java.util.WeakHashMap - -class DestinationsNavOptionsBuilder( - private val jetpackBuilder: NavOptionsBuilder -) { - - var launchSingleTop - get() = jetpackBuilder.launchSingleTop - set(value) { - jetpackBuilder.launchSingleTop = value - } - - var restoreState - get() = jetpackBuilder.restoreState - set(value) { - jetpackBuilder.restoreState = value - } - - val popUpToRoute: String? - get() = jetpackBuilder.popUpToRoute - - fun popUpTo(route: Route, popUpToBuilder: PopUpToBuilder.() -> Unit = {}) { - jetpackBuilder.popUpTo(route.route, popUpToBuilder) - } -} - -private val navigators: WeakHashMap = WeakHashMap() -val NavController.navigator: DestinationsNavigator - get(): DestinationsNavigator { - return navigators[this] ?: DestinationsNavController(this) - .also { navigators[this] = it } - } - /** * Implementation of [DestinationsNavigator] that uses diff --git a/compose-destinations/src/main/java/com/ramcosta/composedestinations/navigation/DestinationsNavOptionsBuilder.kt b/compose-destinations/src/main/java/com/ramcosta/composedestinations/navigation/DestinationsNavOptionsBuilder.kt new file mode 100644 index 00000000..51a83061 --- /dev/null +++ b/compose-destinations/src/main/java/com/ramcosta/composedestinations/navigation/DestinationsNavOptionsBuilder.kt @@ -0,0 +1,48 @@ +package com.ramcosta.composedestinations.navigation + +import androidx.navigation.NavOptionsBuilder +import androidx.navigation.PopUpToBuilder +import com.ramcosta.composedestinations.spec.Route + +/** + * Like [NavOptionsBuilder] but has Compose Destinations friendly + * version of its APIs. + */ +class DestinationsNavOptionsBuilder( + private val jetpackBuilder: NavOptionsBuilder +) { + + /** + * @see [NavOptionsBuilder.launchSingleTop] + */ + var launchSingleTop + get() = jetpackBuilder.launchSingleTop + set(value) { + jetpackBuilder.launchSingleTop = value + } + + /** + * @see [NavOptionsBuilder.restoreState] + */ + var restoreState + get() = jetpackBuilder.restoreState + set(value) { + jetpackBuilder.restoreState = value + } + + /** + * @see [NavOptionsBuilder.popUpToRoute] + */ + val popUpToRoute: String? + get() = jetpackBuilder.popUpToRoute + + /** + * Like [NavOptionsBuilder.popUpTo] but accepting a [com.ramcosta.composedestinations.spec.Route] + * or [com.ramcosta.composedestinations.spec.Direction] to pop up to. + * + * @see [NavOptionsBuilder.popUpTo] + */ + fun popUpTo(route: Route, popUpToBuilder: PopUpToBuilder.() -> Unit = {}) { + jetpackBuilder.popUpTo(route.route, popUpToBuilder) + } +} \ No newline at end of file diff --git a/compose-destinations/src/main/java/com/ramcosta/composedestinations/navigation/NavControllerExt.kt b/compose-destinations/src/main/java/com/ramcosta/composedestinations/navigation/NavControllerExt.kt deleted file mode 100644 index ce0e1023..00000000 --- a/compose-destinations/src/main/java/com/ramcosta/composedestinations/navigation/NavControllerExt.kt +++ /dev/null @@ -1,62 +0,0 @@ -package com.ramcosta.composedestinations.navigation - -import androidx.annotation.MainThread -import androidx.navigation.NavController -import androidx.navigation.NavOptionsBuilder -import androidx.navigation.PopUpToBuilder -import com.ramcosta.composedestinations.spec.Direction -import com.ramcosta.composedestinations.spec.Route - -/** - * Like [NavController.navigate], but uses [Direction] instead of a String route. - */ -fun NavController.navigate( - direction: Direction, - navOptionsBuilder: NavOptionsBuilder.() -> Unit = {} -) { - navigate(direction.route, navOptionsBuilder) -} - -/** - * Like [NavOptionsBuilder.popUpTo] but uses [Route] instead of a String route, making it - * clear what kind of route we need to use and making it more "Compose Destinations friendly". - */ -fun NavOptionsBuilder.popUpTo(route: Route, popUpToBuilder: PopUpToBuilder.() -> Unit = {}) { - popUpTo(route.route, popUpToBuilder) -} - -/** - * Like [NavController.popBackStack] but uses [Route] instead of a String route, making it clear - * what kind of route we need to use and making it more "Compose Destinations friendly". - */ -@MainThread -fun NavController.popBackStack( - route: Route, - inclusive: Boolean, - saveState: Boolean = false -): Boolean = popBackStack(route.route, inclusive, saveState) - -/** - * Like [NavController.clearBackStack] but uses [Route] instead of a String route, making it clear - * what kind of route we need to use and making it more "Compose Destinations friendly". - */ -@MainThread -fun NavController.clearBackStack(route: Route): Boolean = clearBackStack(route.route) - -// region deprecated APIs - -/** - * Like [NavController.navigate], but uses [Direction] instead of a String route. - */ -@Deprecated( - message = "Api will be removed! Use `navigate` extension method instead.", - replaceWith = ReplaceWith("navigate(direction, navOptionsBuilder)") -) -fun NavController.navigateTo( - direction: Direction, - navOptionsBuilder: NavOptionsBuilder.() -> Unit = {} -) { - navigate(direction.route, navOptionsBuilder) -} - -// endregion \ No newline at end of file diff --git a/compose-destinations/src/main/java/com/ramcosta/composedestinations/result/EmptyResultBackNavigator.kt b/compose-destinations/src/main/java/com/ramcosta/composedestinations/result/EmptyResultBackNavigator.kt index f39a2fd7..1d750efa 100644 --- a/compose-destinations/src/main/java/com/ramcosta/composedestinations/result/EmptyResultBackNavigator.kt +++ b/compose-destinations/src/main/java/com/ramcosta/composedestinations/result/EmptyResultBackNavigator.kt @@ -6,9 +6,9 @@ package com.ramcosta.composedestinations.result */ class EmptyResultBackNavigator : ResultBackNavigator { - override fun navigateBack(result: R, onlyIfResumed: Boolean) = Unit + override fun navigateBack(result: R) = Unit override fun setResult(result: R) = Unit - override fun navigateBack(onlyIfResumed: Boolean) = Unit + override fun navigateBack() = Unit } \ No newline at end of file diff --git a/compose-destinations/src/main/java/com/ramcosta/composedestinations/result/ResultBackNavigator.kt b/compose-destinations/src/main/java/com/ramcosta/composedestinations/result/ResultBackNavigator.kt index ce493c7f..c1ff6fe0 100644 --- a/compose-destinations/src/main/java/com/ramcosta/composedestinations/result/ResultBackNavigator.kt +++ b/compose-destinations/src/main/java/com/ramcosta/composedestinations/result/ResultBackNavigator.kt @@ -2,8 +2,6 @@ package com.ramcosta.composedestinations.result import androidx.compose.runtime.* import androidx.navigation.NavController -import com.ramcosta.composedestinations.dynamic.DynamicDestinationSpec -import com.ramcosta.composedestinations.spec.DestinationSpec /** * Navigator that allows navigating back while passing @@ -32,15 +30,16 @@ interface ResultBackNavigator { * * Check [com.ramcosta.composedestinations.result.ResultRecipient] to see * how to get the result. + */ + fun navigateBack(result: R) + + /** + * Goes back to previous destination sending the last result set with [setResult] + * or just navigating if no result was set.. * - * @param onlyIfResumed if true, will ignore the navigation action if the current `NavBackStackEntry` - * is not in the RESUMED state. This avoids duplicate navigation actions. - * By default is false to have the same behaviour as [NavController]. + * It uses [NavController.navigateUp] internally to go back. */ - fun navigateBack( - result: R, - onlyIfResumed: Boolean = false - ) + fun navigateBack() /** * Sets a [result] to be sent on the next [navigateBack] call. @@ -52,16 +51,4 @@ interface ResultBackNavigator { * This also applies if you call [navigateBack] (with result) after calling this. */ fun setResult(result: R) - - /** - * Goes back to previous destination sending the last result set with [setResult] - * or just navigating if no result was set.. - * - * It uses [NavController.navigateUp] internally to go back. - * - * @param onlyIfResumed if true, will ignore the navigation action if the current `NavBackStackEntry` - * is not in the RESUMED state. This avoids duplicate navigation actions. - * By default is false to have the same behaviour as [NavController]. - */ - fun navigateBack(onlyIfResumed: Boolean = false) } diff --git a/compose-destinations/src/main/java/com/ramcosta/composedestinations/result/ResultBackNavigatorImpl.kt b/compose-destinations/src/main/java/com/ramcosta/composedestinations/result/ResultBackNavigatorImpl.kt index d3c1d5e8..b0e304a9 100644 --- a/compose-destinations/src/main/java/com/ramcosta/composedestinations/result/ResultBackNavigatorImpl.kt +++ b/compose-destinations/src/main/java/com/ramcosta/composedestinations/result/ResultBackNavigatorImpl.kt @@ -7,13 +7,11 @@ import androidx.compose.runtime.remember import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.LifecycleOwner -import androidx.navigation.NavBackStackEntry import androidx.navigation.NavController import com.ramcosta.composedestinations.spec.DestinationSpec internal class ResultBackNavigatorImpl( private val navController: NavController, - private val navBackStackEntry: NavBackStackEntry, resultOriginType: Class>, resultType: Class ) : ResultBackNavigator { @@ -21,17 +19,7 @@ internal class ResultBackNavigatorImpl( private val resultKey = resultKey(resultOriginType, resultType) private val canceledKey = canceledKey(resultOriginType, resultType) - private val isResumed: Boolean - get() = navBackStackEntry.lifecycle.currentState == Lifecycle.State.RESUMED - - override fun navigateBack( - result: R, - onlyIfResumed: Boolean - ) { - if (onlyIfResumed && !isResumed) { - return - } - + override fun navigateBack(result: R) { setResult(result) navigateBack() } @@ -43,11 +31,7 @@ internal class ResultBackNavigatorImpl( } } - override fun navigateBack(onlyIfResumed: Boolean) { - if (onlyIfResumed && !isResumed) { - return - } - + override fun navigateBack() { navController.navigateUp() } diff --git a/compose-destinations/src/main/java/com/ramcosta/composedestinations/result/ResultCommons.kt b/compose-destinations/src/main/java/com/ramcosta/composedestinations/result/ResultCommons.kt index d6a268dc..730aeda3 100644 --- a/compose-destinations/src/main/java/com/ramcosta/composedestinations/result/ResultCommons.kt +++ b/compose-destinations/src/main/java/com/ramcosta/composedestinations/result/ResultCommons.kt @@ -6,7 +6,6 @@ import androidx.navigation.NavBackStackEntry import androidx.navigation.NavController import com.ramcosta.composedestinations.dynamic.originalDestination import com.ramcosta.composedestinations.spec.DestinationSpec -import com.ramcosta.composedestinations.spec.DestinationStyle @Composable @PublishedApi @@ -14,13 +13,11 @@ internal fun resultBackNavigator( destination: DestinationSpec<*>, resultType: Class, navController: NavController, - navBackStackEntry: NavBackStackEntry ): ResultBackNavigator { val backNavigator = remember { ResultBackNavigatorImpl( navController = navController, - navBackStackEntry = navBackStackEntry, resultOriginType = destination.originalDestination.javaClass, resultType = resultType ) diff --git a/compose-destinations/src/main/java/com/ramcosta/composedestinations/scope/DestinationScope.kt b/compose-destinations/src/main/java/com/ramcosta/composedestinations/scope/DestinationScope.kt index 9e38c347..c0a72592 100644 --- a/compose-destinations/src/main/java/com/ramcosta/composedestinations/scope/DestinationScope.kt +++ b/compose-destinations/src/main/java/com/ramcosta/composedestinations/scope/DestinationScope.kt @@ -63,7 +63,7 @@ interface DestinationScope: DestinationScopeWithNoDependencies { */ @Composable inline fun DestinationScopeWithNoDependencies<*>.resultBackNavigator(): ResultBackNavigator = - resultBackNavigator(destination, R::class.java, navController, navBackStackEntry) + resultBackNavigator(destination, R::class.java, navController) /** * Returns a well typed [ResultRecipient] for this [DestinationScope] diff --git a/compose-destinations/src/main/java/com/ramcosta/composedestinations/scope/DestinationScopeInternals.kt b/compose-destinations/src/main/java/com/ramcosta/composedestinations/scope/DestinationScopeInternals.kt index d5e13ec9..823ef9d6 100644 --- a/compose-destinations/src/main/java/com/ramcosta/composedestinations/scope/DestinationScopeInternals.kt +++ b/compose-destinations/src/main/java/com/ramcosta/composedestinations/scope/DestinationScopeInternals.kt @@ -10,8 +10,8 @@ import com.ramcosta.composedestinations.navigation.DependenciesContainerBuilder import com.ramcosta.composedestinations.navigation.DestinationDependenciesContainer import com.ramcosta.composedestinations.navigation.DestinationDependenciesContainerImpl import com.ramcosta.composedestinations.navigation.DestinationsNavigator -import com.ramcosta.composedestinations.navigation.navigator import com.ramcosta.composedestinations.spec.DestinationSpec +import com.ramcosta.composedestinations.utils.toDestinationsNavigator @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) abstract class DestinationScopeImpl : DestinationScope { @@ -22,7 +22,7 @@ abstract class DestinationScopeImpl : DestinationScope { } override val destinationsNavigator: DestinationsNavigator - get() = navController.navigator + get() = navController.toDestinationsNavigator() @Composable override fun buildDependencies(): DestinationDependenciesContainer { @@ -46,7 +46,7 @@ abstract class NavGraphBuilderDestinationScopeImpl : NavGraphBuilderDestinati } override fun destinationsNavigator(navController: NavController): DestinationsNavigator { - return navController.navigator + return navController.toDestinationsNavigator() } internal class Default( diff --git a/compose-destinations/src/main/java/com/ramcosta/composedestinations/scope/NavGraphBuilderDestinationScope.kt b/compose-destinations/src/main/java/com/ramcosta/composedestinations/scope/NavGraphBuilderDestinationScope.kt index 52e86294..3e632713 100644 --- a/compose-destinations/src/main/java/com/ramcosta/composedestinations/scope/NavGraphBuilderDestinationScope.kt +++ b/compose-destinations/src/main/java/com/ramcosta/composedestinations/scope/NavGraphBuilderDestinationScope.kt @@ -45,7 +45,7 @@ interface NavGraphBuilderDestinationScope { inline fun NavGraphBuilderDestinationScope<*>.resultBackNavigator( navController: NavController ): ResultBackNavigator = - resultBackNavigator(destination, R::class.java, navController, navBackStackEntry) + resultBackNavigator(destination, R::class.java, navController) /** diff --git a/compose-destinations/src/main/java/com/ramcosta/composedestinations/utils/NavControllerExt.kt b/compose-destinations/src/main/java/com/ramcosta/composedestinations/utils/NavControllerExt.kt new file mode 100644 index 00000000..1a69bedb --- /dev/null +++ b/compose-destinations/src/main/java/com/ramcosta/composedestinations/utils/NavControllerExt.kt @@ -0,0 +1,131 @@ +package com.ramcosta.composedestinations.utils + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.State +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.remember +import androidx.navigation.NavBackStackEntry +import androidx.navigation.NavController +import com.ramcosta.composedestinations.navigation.DestinationsNavController +import com.ramcosta.composedestinations.navigation.DestinationsNavigator +import com.ramcosta.composedestinations.spec.DestinationSpec +import com.ramcosta.composedestinations.spec.NavGraphSpec +import com.ramcosta.composedestinations.spec.Route +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.transform + +/** + * Creates a [DestinationsNavigator] for the [NavController] which has Compose Destinations friendly + * versions of some [NavController] APIs. + */ +fun NavController.toDestinationsNavigator(): DestinationsNavigator { + return DestinationsNavController(this) +} + +/** + * Returns a [DestinationsNavigator] for the [NavController] which has Compose Destinations friendly + * versions of some [NavController] APIs. + */ +@Composable +fun NavController.rememberDestinationsNavigator(): DestinationsNavigator { + return remember(this) { toDestinationsNavigator() } +} + +/** +* The top level navigation graph associated with this [NavController]. +* Can only be called after [com.ramcosta.composedestinations.DestinationsNavHost]. +*/ +val NavController.navGraph: NavGraphSpec + get() { + return NavGraphRegistry[this]?.topLevelNavGraph(this) + ?: error("Cannot call rootNavGraph before DestinationsNavHost!") + } + +/** + * Finds the [DestinationSpec] correspondent to this [NavBackStackEntry]. + * Some [NavBackStackEntry] are not [DestinationSpec], but are [NavGraphSpec] instead. + * If you want a method that works for both, use [route] extension function instead. + * + * Use this ONLY if you're sure your [NavBackStackEntry] corresponds to a [DestinationSpec], + * for example when converting from "current NavBackStackEntry", since a [NavGraphSpec] is never + * the "current destination" shown on screen. + */ +fun NavBackStackEntry.destination(): DestinationSpec<*> { + return when (val route = route()) { + is DestinationSpec<*> -> route + is NavGraphSpec -> error( + "Cannot call `destination()` for a NavBackStackEntry which corresponds to a nav graph, use `route()` instead!" + ) + } +} + +/** + * Finds the [Route] (so either a [DestinationSpec] or a [NavGraphSpec]) + * correspondent to this [NavBackStackEntry]. + */ +fun NavBackStackEntry.route(): Route { + val registry = NavGraphRegistry[this] + ?: error("Cannot call NavBackStackEntry.route() before DestinationsNavHost!") + + val navGraph = registry.navGraph(this) + if (navGraph != null) { + return navGraph + } + + // If it's not a nav graph, then it must have a parent + val parentNavGraph = registry.parentNavGraph(this)!! + return destination.route?.let { parentNavGraph.findDestination(it) } + ?: parentNavGraph.startDestination +} + +/** + * Finds the [NavGraphSpec] that this [NavBackStackEntry] belongs to. + * If [NavBackStackEntry] corresponds to the top level nav graph (i.e, there is no parent), + * then this returns the top level [NavGraphSpec]. + */ +fun NavBackStackEntry.navGraph(): NavGraphSpec { + val registry = NavGraphRegistry[this] + ?: error("Cannot call NavBackStackEntry.parentNavGraph() before DestinationsNavHost!") + + return registry.parentNavGraph(this) ?: route() as NavGraphSpec +} + +/** + * Emits the currently active [DestinationSpec] whenever it changes. If + * there is no active [DestinationSpec], no item will be emitted. + */ +val NavController.currentDestinationFlow: Flow> + get() = currentBackStackEntryFlow.transform { navStackEntry -> + when (val route = navStackEntry.route()) { + is DestinationSpec<*> -> emit(route) + is NavGraphSpec -> Unit + } + } + +/** + * Gets the current [DestinationSpec] as a [State]. + */ +@Composable +fun NavController.currentDestinationAsState(): State?> { + return currentDestinationFlow.collectAsState(initial = null) +} + +/** + * Checks if a given [Route] (which is either [com.ramcosta.composedestinations.spec.NavGraphSpec] + * or [com.ramcosta.composedestinations.spec.DestinationSpec]) is currently somewhere in the back stack. + */ +fun NavController.isRouteOnBackStack(route: Route): Boolean { + return runCatching { getBackStackEntry(route.route) }.isSuccess +} + +/** + * Same as [isRouteOnBackStack] but provides a [State] which you can use to make sure + * your Composables get recomposed when this changes. + */ +@Composable +fun NavController.isRouteOnBackStackAsState(route: Route): State { + return remember(currentBackStackEntryFlow) { + currentBackStackEntryFlow.map { isRouteOnBackStack(route) } + }.collectAsState(initial = isRouteOnBackStack(route)) +} \ No newline at end of file diff --git a/compose-destinations/src/main/java/com/ramcosta/composedestinations/utils/SpecExtensions.kt b/compose-destinations/src/main/java/com/ramcosta/composedestinations/utils/SpecExtensions.kt index acc04fc3..3ecca076 100644 --- a/compose-destinations/src/main/java/com/ramcosta/composedestinations/utils/SpecExtensions.kt +++ b/compose-destinations/src/main/java/com/ramcosta/composedestinations/utils/SpecExtensions.kt @@ -1,115 +1,9 @@ package com.ramcosta.composedestinations.utils -import androidx.compose.runtime.Composable -import androidx.compose.runtime.State -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.remember import androidx.navigation.NavBackStackEntry -import androidx.navigation.NavController import com.ramcosta.composedestinations.spec.DestinationSpec import com.ramcosta.composedestinations.spec.NavGraphSpec import com.ramcosta.composedestinations.spec.Route -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.transform - -/** - * The top level navigation graph associated with this [NavController]. - * Can only be called after [com.ramcosta.composedestinations.DestinationsNavHost]. - */ -val NavController.navGraph: NavGraphSpec - get() { - return NavGraphRegistry[this]?.topLevelNavGraph(this) - ?: error("Cannot call rootNavGraph before DestinationsNavHost!") - } - -/** - * Finds the [DestinationSpec] correspondent to this [NavBackStackEntry]. - * Some [NavBackStackEntry] are not [DestinationSpec], but are [NavGraphSpec] instead. - * If you want a method that works for both, use [route] extension function instead. - * - * Use this ONLY if you're sure your [NavBackStackEntry] corresponds to a [DestinationSpec], - * for example when converting from "current NavBackStackEntry", since a [NavGraphSpec] is never - * the "current destination" shown on screen. - */ -fun NavBackStackEntry.destination(): DestinationSpec<*> { - return when (val route = route()) { - is DestinationSpec<*> -> route - is NavGraphSpec -> error( - "Cannot call `destination()` for a NavBackStackEntry which corresponds to a nav graph, use `route()` instead!" - ) - } -} - -/** - * Finds the [Route] (so either a [DestinationSpec] or a [NavGraphSpec]) - * correspondent to this [NavBackStackEntry]. - */ -fun NavBackStackEntry.route(): Route { - val registry = NavGraphRegistry[this] - ?: error("Cannot call NavBackStackEntry.route() before DestinationsNavHost!") - - val navGraph = registry.navGraph(this) - if (navGraph != null) { - return navGraph - } - - // If it's not a nav graph, then it must have a parent - val parentNavGraph = registry.parentNavGraph(this)!! - return destination.route?.let { parentNavGraph.findDestination(it) } - ?: parentNavGraph.startDestination -} - -/** - * Finds the [NavGraphSpec] that this [NavBackStackEntry] belongs to. - * If [NavBackStackEntry] corresponds to the top level nav graph (i.e, there is no parent), - * then this returns the top level [NavGraphSpec]. - */ -fun NavBackStackEntry.navGraph(): NavGraphSpec { - val registry = NavGraphRegistry[this] - ?: error("Cannot call NavBackStackEntry.parentNavGraph() before DestinationsNavHost!") - - return registry.parentNavGraph(this) ?: route() as NavGraphSpec -} - -/** - * Emits the currently active [DestinationSpec] whenever it changes. If - * there is no active [DestinationSpec], no item will be emitted. - */ -val NavController.currentDestinationFlow: Flow> - get() = currentBackStackEntryFlow.transform { navStackEntry -> - when (val route = navStackEntry.route()) { - is DestinationSpec<*> -> emit(route) - is NavGraphSpec -> Unit - } - } - -/** - * Gets the current [DestinationSpec] as a [State]. - */ -@Composable -fun NavController.currentDestinationAsState(): State?> { - return currentDestinationFlow.collectAsState(initial = null) -} - -/** - * Checks if a given [Route] (which is either [com.ramcosta.composedestinations.spec.NavGraphSpec] - * or [com.ramcosta.composedestinations.spec.DestinationSpec]) is currently somewhere in the back stack. - */ -fun NavController.isRouteOnBackStack(route: Route): Boolean { - return runCatching { getBackStackEntry(route.route) }.isSuccess -} - -/** - * Same as [isRouteOnBackStack] but provides a [State] which you can use to make sure - * your Composables get recomposed when this changes. - */ -@Composable -fun NavController.isRouteOnBackStackAsState(route: Route): State { - return remember(currentBackStackEntryFlow) { - currentBackStackEntryFlow.map { isRouteOnBackStack(route) } - }.collectAsState(initial = isRouteOnBackStack(route)) -} /** * If this [Route] is a [DestinationSpec], returns it diff --git a/playground/src/main/java/com/ramcosta/samples/playground/PlaygroundApp.kt b/playground/src/main/java/com/ramcosta/samples/playground/PlaygroundApp.kt index 0f1fb982..4ee6ffc6 100644 --- a/playground/src/main/java/com/ramcosta/samples/playground/PlaygroundApp.kt +++ b/playground/src/main/java/com/ramcosta/samples/playground/PlaygroundApp.kt @@ -7,7 +7,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.navigation.compose.rememberNavController -import com.ramcosta.composedestinations.navigation.navigate +import com.ramcosta.composedestinations.utils.rememberDestinationsNavigator import com.ramcosta.samples.playground.commons.DrawerControllerImpl import com.ramcosta.samples.playground.commons.composables.BottomBar import com.ramcosta.samples.playground.commons.composables.MyDrawer @@ -23,6 +23,7 @@ fun PlaygroundApp(testProfileDeepLink: () -> Unit) { val scaffoldState = rememberScaffoldState() val coroutineScope = rememberCoroutineScope() val navController = rememberNavController() + val navigator = navController.rememberDestinationsNavigator() PlaygroundScaffold( scaffoldState = scaffoldState, @@ -31,14 +32,14 @@ fun PlaygroundApp(testProfileDeepLink: () -> Unit) { TopBar( destination = destination, onDrawerClick = { coroutineScope.launch { scaffoldState.drawerState.open() } }, - onSettingsClick = { navController.navigate(NavGraphs.settings) } + onSettingsClick = { navigator.navigate(NavGraphs.settings) } ) }, bottomBar = { destination -> BottomBar( currentDestination = destination, onBottomBarItemClick = { - navController.navigate(it) { + navigator.navigate(it) { launchSingleTop = true } } diff --git a/playground/src/main/java/com/ramcosta/samples/playground/commons/composables/MyDrawer.kt b/playground/src/main/java/com/ramcosta/samples/playground/commons/composables/MyDrawer.kt index f2cc9053..0a0a6623 100644 --- a/playground/src/main/java/com/ramcosta/samples/playground/commons/composables/MyDrawer.kt +++ b/playground/src/main/java/com/ramcosta/samples/playground/commons/composables/MyDrawer.kt @@ -4,6 +4,7 @@ import androidx.compose.material.ScaffoldState import androidx.compose.runtime.Composable import androidx.lifecycle.Lifecycle import androidx.navigation.NavHostController +import com.ramcosta.composedestinations.utils.toDestinationsNavigator import com.ramcosta.samples.playground.commons.DrawerContent import com.ramcosta.samples.playground.ui.screens.NavGraphs import com.ramcosta.samples.playground.ui.screens.appDestination @@ -12,7 +13,6 @@ import com.ramcosta.samples.playground.ui.screens.destinations.DirectionDestinat import com.ramcosta.samples.playground.ui.screens.startAppDestination import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch -import com.ramcosta.composedestinations.navigation.navigate as cdNavigate @Composable fun MyDrawer( @@ -31,7 +31,7 @@ fun MyDrawer( if (navController.currentBackStackEntry?.lifecycle?.currentState == Lifecycle.State.RESUMED && navController.currentBackStackEntry?.appDestination() != clickedDestination ) { - navController.cdNavigate(clickedDestination) + navController.toDestinationsNavigator().navigate(clickedDestination) coroutineScope.launch { scaffoldState.drawerState.close() } } }