What it is
Voyager is a navigation library for Compose Multiplatform with type-safe screens, nested navigators, tabs and integrated screen-scoped models.
A Screen is a class implementing a Content composable. Navigation pushes and pops instances, so arguments are constructor parameters rather than encoded strings.
Installation
implementation("cafe.adriel.voyager:voyager-navigator:1.1.0-beta02")Getting started
The smallest useful thing you can do with it, and what each part means.
kotlin
data class BookDetailScreen(val bookId: Int) : Screen {
@Composable
override fun Content() {
val navigator = LocalNavigator.currentOrThrow
val model = rememberScreenModel { BookDetailModel(bookId) }
val state by model.state.collectAsState()
BookDetail(state, onBack = navigator::pop)
}
}
// Arguments are constructor parameters — no string routes to mistype.
navigator.push(BookDetailScreen(bookId = 42))
navigator.pop()
navigator.replaceAll(HomeScreen)
Navigator(HomeScreen) { navigator ->
SlideTransition(navigator)
}Advanced usage
Where the library earns its place over a simpler alternative.
kotlin
class BookDetailModel(private val id: Int) : ScreenModel {
private val _state = MutableStateFlow<UiState>(UiState.Loading)
val state = _state.asStateFlow()
init {
// screenModelScope is cancelled when the screen is popped.
screenModelScope.launch { _state.value = load(id) }
}
}
object LibraryTab : Tab {
override val options: TabOptions
@Composable get() = TabOptions(index = 0u, title = "Library")
@Composable override fun Content() = Navigator(BookListScreen)
}
TabNavigator(LibraryTab) {
Scaffold(bottomBar = { TabRow(...) }, content = { CurrentTab() })
}Errors and fixes
The failures you are most likely to hit, and what actually resolves them.
- LocalNavigator is null
- The composable is outside a Navigator. Use currentOrThrow inside Screen.Content, not in a detached composable.
- Screen state is lost on rotation
- Screens must be serialisable for Android state restoration. Use data classes with primitive parameters.
Best practices
- Keep Screen classes serialisable-friendly if you need state restoration on Android.
- Use rememberScreenModel rather than remembering a model directly, so it survives recomposition.
- Nest Navigators per tab so each keeps its own back stack.
- Prefer Jetpack Navigation on Android-only projects; Voyager's advantage is Multiplatform.
Background
Why it exists, and what it was reacting to.
Jetpack Navigation is Android-only and string-route based. Voyager offers navigation as ordinary Kotlin objects, which works across Multiplatform targets and is type-safe by construction.
