Your First Screen
This page walks through a complete, working example: two screens that navigate to each other, plus a third screen that returns a result. By the end you’ll have touched every concept covered in Basic Concepts.
If you’d rather read the finished code, the equivalent runnable example lives in recipes/basic/BasicNavigation.kt and recipes/results/ReturningResults.kt.
1. Define the keys
A key is a contract — what does this screen need, and what does it produce?
@Serializable
data object Home : NavigationKey
@Serializable
data class Profile(val userId: String) : NavigationKey
@Serializable
data object PickName : NavigationKey.WithResult<String>
Home takes no inputs. Profile requires a userId. PickName produces a String back to whoever opened it.
2. Implement the destinations
Each destination is annotated with @NavigationDestination(KeyClass::class). The simplest form is a @Composable function.
@Composable
@NavigationDestination(Home::class)
fun HomeScreen() {
val navigation = navigationHandle<Home>()
val askForName = registerForNavigationResult<String>(
onCompleted = { name -> /* we'll use it in a moment */ },
)
Column {
Text("Welcome home")
Button(onClick = { navigation.open(Profile("user-123")) }) {
Text("View profile")
}
Button(onClick = { askForName.open(PickName) }) {
Text("Pick a name")
}
}
}
@Composable
@NavigationDestination(Profile::class)
fun ProfileScreen() {
val navigation = navigationHandle<Profile>()
Column {
Text("Profile for ${navigation.key.userId}")
Button(onClick = { navigation.close() }) { Text("Back") }
}
}
@Composable
@NavigationDestination(PickName::class)
fun PickNameScreen() {
val navigation = navigationHandle<PickName>()
var input by remember { mutableStateOf("") }
Column {
TextField(value = input, onValueChange = { input = it })
Button(onClick = { navigation.complete(input) }) {
Text("Done")
}
Button(onClick = { navigation.close() }) {
Text("Cancel")
}
}
}
A few things worth noticing:
navigationHandle<T>()returns aNavigationHandlethat knows the type of the current key —navigation.keyis typedProfileinsideProfileScreen.navigation.open(otherKey)adds another destination on top of this one.navigation.close()removes the current destination.navigation.complete(value)is the result-returning form ofclose()— only callable on a destination whose key implementsNavigationKey.WithResult<T>.registerForNavigationResult<T>returns a channel;openit with a key andonCompletedis called with the result. There’s alsoonClosedfor when the user dismisses without producing a result.
3. Wire up the NavigationComponent
Declare a NavigationComponent once for your application:
@NavigationComponent
object MyComponent : NavigationComponentConfiguration(
module = createNavigationModule { }
)
KSP generates an installNavigationController extension on this object for each platform you target. On Android, install it in your Application:
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
MyComponent.installNavigationController(this)
}
}
See Installation for the equivalent on iOS, Desktop and Web.
4. Host the backstack
Decide where your navigation lives in the UI. The minimal pattern is a single container at the root of your Compose tree:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val container = rememberNavigationContainer(
backstack = backstackOf(Home.asInstance()),
)
NavigationDisplay(state = container)
}
}
}
backstackOf(Home.asInstance()) says “start with Home on the stack”. NavigationDisplay watches the container’s backstack and renders the current destination, animating transitions for you.
5. Run it
Open the app. You’re on Home. Tap View profile — the backstack pushes Profile, and NavigationDisplay animates the transition. Tap Back — Profile closes and you’re back on Home.
Tap Pick a name — PickName opens. Type something and tap Done. The onCompleted callback fires on Home with the string you typed.
What’s next
That’s a working app. From here:
- For different presentation styles (dialogs, bottom sheets, custom overlays), see Navigation Destinations.
- For multiple containers, tabs, or nested back stacks, see Navigation Containers.
- For richer result handling, see Results.
- For custom transitions and per-element animations, see Animations.
- For testing, see Testing.