NavController¶
This is the main control point of navigation. It keeps record of all current backstack entries and preserves them on activity/process recreation.
NavController may be created with rememberNavController
method in a composable function or with navController
outside of composition. The latter may be used for storing NavController in a ViewModel. As it implements Parcelable interface, it could be stored in a SavedStateHandle.
Both rememberNavController
and navController
methods accept startDestination
as a parameter:
val navController = rememberNavController<Destination>(
startDestination = Destination.First
)
If you want to create NavController with an arbitrary number of backstack items, you may use initialBackstack
parameter instead:
val navController = rememberNavController<Destination>(
initialBackstack = listOf(Destination.First, Destination.Second, Destination.Third)
)
Destination.Third
will become the currently displayed item. Destination.First
and Destination.Second
will be stored in the backstack.
If you want to store NavController in a ViewModel use saveable
delegate for SavedStateHandle:
class NavigationViewModel(savedStateHandle: SavedStateHandle) : ViewModel() {
val navController by savedStateHandle.saveable<NavController<Destination>> {
navController(startDestination = Destination.First)
}
}
Destinations¶
NavController accepts all types that meet the requirements as destinations:
-
The type must be writable to Parcel - it could be Parcelable, Serializable, string/primitive, or other supported type.
-
The type must be Stable, Immutable, or string/primitive type.
Other than that, you are not limited to any particular type.
Tip
It is very convenient to define your set of destinations as a sealed class or enum. This way you will always be notified by the compiler that you have a non-exhaustive when
statement if you add a new destination.
Tip
You may also define your own base interface for destinations, for example:
interface Destination : Parcelable {
@Composable
fun Content()
}
This way you may handle each destinations without checking its instance:
NavHost(navController) { it.Content() }
NavEntry¶
In order to be passed into NavController, each destination should be wrapped into NavEntry. It contains a unique identifier which is used to properly preserve saved state and manage Android architecture components (Lifecycle, ViewModelStore and SavedStateRegistry) for each such entry inside NavHost.
Saved state and view models of each entry are guaranteed to be preserved for as long as the associated entry is present in the backstack.
Note
If you add two equal destinations to the backstack, wrapped into two different entries, they will get their own separate identities, saved states and components. However, it is possible to put same exact entry instance into the backstack and it will be correctly treated as the same entry.
Navigation methods¶
There is a handful of pre-defined methods suitable for basic app navigation: navigate
, moveToTop
, pop
, popUpTo
, popAll
, replaceLast
, replaceUpTo
, replaceAll
. They all are pretty much self-explanatory, except maybe moveToTop
.
moveToTop
method searches for some particular destination in the backstack and moves it to the top, effectively making it the currently displayed destination. This is particularly useful for integration with BottomNavigation/TabRow, when you want to always keep a single instance of every destination in the backstack.
The method is expected to be used in pair with navigate
:
if (!navController.moveToTop { it is SomeDestination }) {
// navigate to a new destination if there is no existing one
navController.navigate(SomeDestination())
}
You may see how it is used for BottomNavigation in the sample.
Methods with a search predicate¶
moveToTop
, popUpTo
, replaceUpTo
methods require the predicate
parameter to be specified. It provides a selection condition for a destination to search for.
In case multiple destinations match the predicate, you may specify the match
parameter. Match.Last
is the default value and in this case the last matching item from the start of the backstack will be selected. Alternatively, you may use Match.First
.
New custom methods¶
If your use-case calls for some advanced backstack manipulations, you may use setNewBackstack
method. It is in fact the only public method defined in NavController, all other methods are provided as extensions and use setNewBackstack
under the hood. Here is how a new extension method moveLastEntryToStart
is implemented in the sample:
fun NavController<BottomNavigationDestination>.moveLastEntryToStart() {
setNewBackstack(
entries = backstack.entries.toMutableList().also {
val entry = it.removeLast()
it.add(0, entry)
},
action = NavAction.Pop
)
}
NavBackstack¶
You may access current backstack entries and the last NavAction through backstack
property of NavController. This property is backed up by MutableState and any changes to it will notify composition.