Voyager : Compose Multiplatform Navigation and ViewModels (ScreenModel)

Italo Melo
3 min readApr 14, 2024

--

Portuguese Version

Designed for Jetpack Compose, Voyager simplifies navigation and viewModels (screenModel) management across platforms. In this article, we’ll explore Voyager’s core principles, features, and how it reshapes multiplatform UI development.

Setup

Voyager provides multiple dependencies for your desire, each one with a feature :

dependencies {
val voyagerVersion = "1.0.0"

// Multiplatform

// Navigator
implementation("cafe.adriel.voyager:voyager-navigator:$voyagerVersion")

// Screen Model
implementation("cafe.adriel.voyager:voyager-screenmodel:$voyagerVersion")

// BottomSheetNavigator
implementation("cafe.adriel.voyager:voyager-bottom-sheet-navigator:$voyagerVersion")

// TabNavigator
implementation("cafe.adriel.voyager:voyager-tab-navigator:$voyagerVersion")

// Transitions
implementation("cafe.adriel.voyager:voyager-transitions:$voyagerVersion")

// Android

// Koin integration
implementation("cafe.adriel.voyager:voyager-koin:$voyagerVersion")

// Hilt integration
implementation("cafe.adriel.voyager:voyager-hilt:$voyagerVersion")

// LiveData integration
implementation("cafe.adriel.voyager:voyager-livedata:$voyagerVersion")

// Desktop + Android

// Kodein integration
implementation("cafe.adriel.voyager:voyager-kodein:$voyagerVersion")

// RxJava integration
implementation("cafe.adriel.voyager:voyager-rxjava:$voyagerVersion")
}

For this tutorial we are going to use the Navigator and ScreenModel , so we can start adding these implementations to our version catalog file :

[versions]
voyager = "1.0.0"

[libraries]
voyager-navigator = { module = "cafe.adriel.voyager:voyager-navigator", version.ref = "voyager" }
voyager-screenModel = { module = "cafe.adriel.voyager:voyager-screenmodel", version.ref = "voyager" }

Now lets import these new libraries on our compose build.gradle file:

commonMain.dependencies {
implementation(libs.voyager)
implementation(libs.voyager.screenModel)
}

Understanding ScreenModel and ViewModel

ScreenModel is just like a ViewModel: designed to store and manage UI-related data in a lifecycle conscious way. It also allows data to survive configuration changes such as screen rotations. (Voyager docs)

With that in mind lets create a simple ScreenModel to fetch some data :

import cafe.adriel.voyager.core.model.ScreenModel
import cafe.adriel.voyager.core.model.screenModelScope

class DataScreenModel() : ScreenModel {
fun fetchData(){
screenModelScope.launch {
//fetch data code
}
}
}

Note that we are using screenModelScope instead of viewModelScope, but they behave similarly.

That’s it, this is a simple implementation of a screenModel

Navigating screens

First let’s wrap our main composable app on a Navigator , and on the constructor pass the first screen that we want to display, in this case will be DataScreen

import cafe.adriel.voyager.navigator.Navigator

@Composable
fun App() {
Navigator(DataScreen())
}

Now, lets create our DataScreen by implementing Screen interface and overriding the content function (this will be displayed on your screen), also on this screen we want to navigate to another screen, so in order to do this, we want to import the LocalNavigator and call the push function in order to push a new screen passing the required parameters

import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.navigator.LocalNavigator

class DataScreen : Screen {
@Composable
override fun Content {
val navigator = LocalNavigator.currentOrThrow
// on a button click or event call this :
navigator.push(DataDetailsScreen(data))
}
}

In order to get the parameters, our second screen will be a data class , this will be the result :

import cafe.adriel.voyager.core.screen.Screen

data class DataDetailScreen(data : Data) : Screen {
@Composable
override fun Content {
// display data on composable
// on a button click or event call this :
navigator.pop()
}
}

If you want to go back to the previous screen use the navigator.pop() function

Transitions

If you want to add transitions to this navigator, first we need to add the transitions dependency

[versions]
voyager = "1.0.0"

[libraries]
voyager-transitions = { module = "cafe.adriel.voyager:voyager-transitions", version.ref = "voyager" }
commonMain.dependencies {
implementation(libs.voyager.transitions)
}

Now let’s apply the transition on our Navigator component on the App Composable :

@Composable
fun App() {
Navigator(DataScreen()) { navigator ->
SlideTransition(navigator)
}
}

In this case we are using the SlideTransition , to see the available transitions check the documentation

Thats it! This is a simple implementation of Voyager, keep in mind that there are multiple implementations and support for koin , hilt , codein , etc, this is a very well documented library so visit the documentation for further details

--

--