Voyager: The Navigation Library for Kotlin Multiplatform and Compose Multiplatform

Voyager: The Navigation Library for Kotlin Multiplatform and Compose Multiplatform

Voyager: The Navigation Library for Kotlin Multiplatform and Compose Multiplatform

Learn how Voyager handles navigation in a cross-platform app

Voyager: The Navigation Library for Kotlin Multiplatform and Compose Multiplatform
Voyager: The Navigation Library for Kotlin Multiplatform and Compose Multiplatform

Learn how Voyager handles navigation in a cross-platform app

Learn how Voyager handles navigation in a cross-platform app

Learn how Voyager handles navigation in a cross-platform app

Introduction

Voyager is a new navigation library built specifically for Compose multiplatform, which is designed to make navigation in Compose-based apps simpler and more intuitive.

It offers a composable API that allows developers to create screens with minimal effort, and provides an easy-to-use navigation stack that supports push, pop, replace, and other common navigation operations.

With Voyager, developers can build complex, multi-screen apps with ease, and can take advantage of Compose’s powerful tooling and state management capabilities to create responsive and performant UIs.

Open source template

Before continuing, we made an open source template for Kotlin multiplatform and Compose Multiplatform. Navigation with Voyager is setup. But that’s not all : UI, DI, offline cache, multiplatform localization and resources. All of this is setup.

Open source template with voyager

This is a version of AppKickstarter with very limited features but yet useful setup.

Back to our guide.

Why it’s important for Compose multiplatform applications

Voyager offers a unified approach to navigation in a multiplatform Compose project.

By using Voyager in the shared module of the app, there’s no need to duplicate the navigation implementation for each platform, reducing the development effort and improving the code maintainability.

Additionally, Voyager offers seamless integration with Compose’s lifecycle and state management, making it a powerful and efficient solution for multiplatform navigation in Jetpack Compose.

Basic setup of Voyager

In your MainActivity (or any other activity that uses Jetpack Compose), use the Navigator composable to set up your app’s navigation:

class MainActivity : AppCompatActivity() {
  override fun onCreate(savedInstanceState: Bundle?) { 
    super.onCreate(savedInstanceState) 
    setContent { 
      Navigator(HomeScreen) 
    }
  }
}

Use the push method of the Navigator instance to navigate to a new screen. Here is an example of how you can navigate from the HomeScreen to the DetailScreen:

data class DetailScreen(private val itemId: Int) : Screen {
  @Composable override fun Content() {
    // Your detail screen content goes here 
  } 
}

class HomeScreen : Screen {
  @Composable override fun Content() { 
    val navigator = LocalNavigator.current 
    Button(
      onClick = { navigator.push(DetailScreen(itemId = 123)) }
    ) { 
      Text("View details") 
    } 
  } 
}

This code sets up the Navigator with the HomeScreen as the initial screen. The push method is used to navigate to the DetailScreen when the user taps the “View details” button. The LocalNavigator is used to get a reference to the current navigator instance.

Screen models, state management, coroutine and Koin integration

State management is a crucial part of any mobile app. Voyager’s StateScreenModel is a helpful tool for managing screen state and updating the UI based on changes to that state. The StateScreenModel class extends the ScreenModel interface and provides a mutable MutableState property that can be used to hold and update the current state of the screen.

To use the StateScreenModel in a Voyager screen, you can define a sealed class that represents the possible states for your screen. For example:

sealed class PersonScreenState { 
  object Init: PersonScreenState() 
  object Loading : PersonScreenState() 
  data class Content(val person: Person) : PersonScreenState() 
}

Then, define a StateScreenModel that takes the initial state as a parameter:

class PersonDetailScreenModel(private val repository: PersonRepository) : StateScreenModel<MyScreenState>(initialState) {
  fun getPerson(id: String) { 
    coroutineScope.launch { 
      mutableState.value = State.Loading 
      mutableState.value = State.Result(person = getPerson(id)) 
    }
  }
}

One of the benefits of using Voyager’s StateScreenModel is the reduction in code required for state management.

Since StateScreenModel integrates a mutable state, there’s no need to manually define a public StateFlow for the state and a private mutableStateFlow for the same state. This saves developers from writing a lot of template code and reduces the likelihood of errors.

Coroutines integration

As you can see in this code, Voyager integrates seamlessly with coroutines.

It makes it easy to manage asynchronous operations within screen models.

Screen models in Voyager come with a coroutineScope property that allows you to launch coroutines and ensure they are properly cancelled when the screen model is disposed.

The getPerson function launches a coroutine using coroutineScope.launch. This allows you to perform asynchronous operations such as network requests, database queries, and other time-consuming tasks without blocking the UI thread.

Integration of dependency injection with Koin

You can then use this StateScreenModel in your screen and update the state as needed, which will automatically trigger a recomposition of the UI.

In your screen, you can then inject the screen model using the getScreenModel() function from the koin voyager library. Here is an example:

class PersonDetailScreen(val id: String) : Screen { 
  @Composable override fun Content() { 
    val screenModel = getScreenModel<PersonDetailScreenModel>() { parametersOf(id) } 
    val state by screenModel.state.collectAsState() 
    when (state) { 
      is State.Loading -> LoadingContent() 
      is State.Result -> PersonContent(state.post)
    }
  }
}

Voyager integrates well with the Koin dependency injection framework. It allows for easy management and injection of dependencies into screen models. To use Koin with Voyager, you will need to include the Koin dependency in your project and define your Koin modules.

To define a Koin module for a screen model, you can use the factory component and provide a lambda that returns an instance of the screen model. Here is an example:

val homeModule = module { factory { HomeScreenModel() } }

Transition

Voyager comes with a built-in transition mechanism that allows you to create smooth transitions between screens.

To use the transition mechanism, you can pass a Transition instance to the Navigator composable.

The Transition instance defines how the transition should be animated.

For example, the following code shows how to use the SlideTransition to animate the transition between screens:

setContent { Navigator(HomeScreen) { navigator -> SlideTransition(navigator) } }

In this example, the SlideTransition is used to animate the transition between screens. The SlideTransition takes the navigator object as an argument, which is used to get the current screen and the previous screen. The SlideTransition then animates the transition between these screens by sliding the new screen in from the right.

There are several other built-in transitions available in Voyager, such as the FadeTransition and the ScaleTransition. You can also create your own custom transitions by implementing the Transition interface.

Why we love Voyager

At AppKickstarter, we love using Voyager because it is an efficient solution for implementing navigation in our Kotlin Multiplatform projects. Voyager solves the problem of duplicate navigation code in our Android and iOS projects by providing a shared navigation module.

Additionally, its integration with popular Kotlin tools such as Koin and Coroutines makes it easy to manage dependencies and asynchronous tasks.

Open Source template with Voyager

We offer a free and open source template for building multiplatform apps using Compose multiplatform and Voyager.

Our template includes everything you need to get started, from setting up Voyager to defining screens and injecting dependencies using Koin.

Upgrade Your Cross-Platform App Development with AppKickstarter

For those looking for additional features and dedicated support, we also offer a premium version called AppKickstarter.

With AppKickstarter, you’ll have access to features such as authentication with Firebase, data storage with Firebase and SqlDelight, onboarding screens, and payment integration with RevenueCat.

Our team is dedicated to providing you with the support you need to build the best possible cross-platform app.

If you’re interested in learning more about AppKickstarter and how it can help you take your project to the next level, please visit our website to learn more and purchase your license today.

Upgrade with AppKickstarter

We believe that AppKickstarter is the ultimate tool for building high-quality cross-platform apps, and we’re confident that you’ll love using it as much as we do.

Introduction

Voyager is a new navigation library built specifically for Compose multiplatform, which is designed to make navigation in Compose-based apps simpler and more intuitive.

It offers a composable API that allows developers to create screens with minimal effort, and provides an easy-to-use navigation stack that supports push, pop, replace, and other common navigation operations.

With Voyager, developers can build complex, multi-screen apps with ease, and can take advantage of Compose’s powerful tooling and state management capabilities to create responsive and performant UIs.

Open source template

Before continuing, we made an open source template for Kotlin multiplatform and Compose Multiplatform. Navigation with Voyager is setup. But that’s not all : UI, DI, offline cache, multiplatform localization and resources. All of this is setup.

Open source template with voyager

This is a version of AppKickstarter with very limited features but yet useful setup.

Back to our guide.

Why it’s important for Compose multiplatform applications

Voyager offers a unified approach to navigation in a multiplatform Compose project.

By using Voyager in the shared module of the app, there’s no need to duplicate the navigation implementation for each platform, reducing the development effort and improving the code maintainability.

Additionally, Voyager offers seamless integration with Compose’s lifecycle and state management, making it a powerful and efficient solution for multiplatform navigation in Jetpack Compose.

Basic setup of Voyager

In your MainActivity (or any other activity that uses Jetpack Compose), use the Navigator composable to set up your app’s navigation:

class MainActivity : AppCompatActivity() {
  override fun onCreate(savedInstanceState: Bundle?) { 
    super.onCreate(savedInstanceState) 
    setContent { 
      Navigator(HomeScreen) 
    }
  }
}

Use the push method of the Navigator instance to navigate to a new screen. Here is an example of how you can navigate from the HomeScreen to the DetailScreen:

data class DetailScreen(private val itemId: Int) : Screen {
  @Composable override fun Content() {
    // Your detail screen content goes here 
  } 
}

class HomeScreen : Screen {
  @Composable override fun Content() { 
    val navigator = LocalNavigator.current 
    Button(
      onClick = { navigator.push(DetailScreen(itemId = 123)) }
    ) { 
      Text("View details") 
    } 
  } 
}

This code sets up the Navigator with the HomeScreen as the initial screen. The push method is used to navigate to the DetailScreen when the user taps the “View details” button. The LocalNavigator is used to get a reference to the current navigator instance.

Screen models, state management, coroutine and Koin integration

State management is a crucial part of any mobile app. Voyager’s StateScreenModel is a helpful tool for managing screen state and updating the UI based on changes to that state. The StateScreenModel class extends the ScreenModel interface and provides a mutable MutableState property that can be used to hold and update the current state of the screen.

To use the StateScreenModel in a Voyager screen, you can define a sealed class that represents the possible states for your screen. For example:

sealed class PersonScreenState { 
  object Init: PersonScreenState() 
  object Loading : PersonScreenState() 
  data class Content(val person: Person) : PersonScreenState() 
}

Then, define a StateScreenModel that takes the initial state as a parameter:

class PersonDetailScreenModel(private val repository: PersonRepository) : StateScreenModel<MyScreenState>(initialState) {
  fun getPerson(id: String) { 
    coroutineScope.launch { 
      mutableState.value = State.Loading 
      mutableState.value = State.Result(person = getPerson(id)) 
    }
  }
}

One of the benefits of using Voyager’s StateScreenModel is the reduction in code required for state management.

Since StateScreenModel integrates a mutable state, there’s no need to manually define a public StateFlow for the state and a private mutableStateFlow for the same state. This saves developers from writing a lot of template code and reduces the likelihood of errors.

Coroutines integration

As you can see in this code, Voyager integrates seamlessly with coroutines.

It makes it easy to manage asynchronous operations within screen models.

Screen models in Voyager come with a coroutineScope property that allows you to launch coroutines and ensure they are properly cancelled when the screen model is disposed.

The getPerson function launches a coroutine using coroutineScope.launch. This allows you to perform asynchronous operations such as network requests, database queries, and other time-consuming tasks without blocking the UI thread.

Integration of dependency injection with Koin

You can then use this StateScreenModel in your screen and update the state as needed, which will automatically trigger a recomposition of the UI.

In your screen, you can then inject the screen model using the getScreenModel() function from the koin voyager library. Here is an example:

class PersonDetailScreen(val id: String) : Screen { 
  @Composable override fun Content() { 
    val screenModel = getScreenModel<PersonDetailScreenModel>() { parametersOf(id) } 
    val state by screenModel.state.collectAsState() 
    when (state) { 
      is State.Loading -> LoadingContent() 
      is State.Result -> PersonContent(state.post)
    }
  }
}

Voyager integrates well with the Koin dependency injection framework. It allows for easy management and injection of dependencies into screen models. To use Koin with Voyager, you will need to include the Koin dependency in your project and define your Koin modules.

To define a Koin module for a screen model, you can use the factory component and provide a lambda that returns an instance of the screen model. Here is an example:

val homeModule = module { factory { HomeScreenModel() } }

Transition

Voyager comes with a built-in transition mechanism that allows you to create smooth transitions between screens.

To use the transition mechanism, you can pass a Transition instance to the Navigator composable.

The Transition instance defines how the transition should be animated.

For example, the following code shows how to use the SlideTransition to animate the transition between screens:

setContent { Navigator(HomeScreen) { navigator -> SlideTransition(navigator) } }

In this example, the SlideTransition is used to animate the transition between screens. The SlideTransition takes the navigator object as an argument, which is used to get the current screen and the previous screen. The SlideTransition then animates the transition between these screens by sliding the new screen in from the right.

There are several other built-in transitions available in Voyager, such as the FadeTransition and the ScaleTransition. You can also create your own custom transitions by implementing the Transition interface.

Why we love Voyager

At AppKickstarter, we love using Voyager because it is an efficient solution for implementing navigation in our Kotlin Multiplatform projects. Voyager solves the problem of duplicate navigation code in our Android and iOS projects by providing a shared navigation module.

Additionally, its integration with popular Kotlin tools such as Koin and Coroutines makes it easy to manage dependencies and asynchronous tasks.

Open Source template with Voyager

We offer a free and open source template for building multiplatform apps using Compose multiplatform and Voyager.

Our template includes everything you need to get started, from setting up Voyager to defining screens and injecting dependencies using Koin.

Upgrade Your Cross-Platform App Development with AppKickstarter

For those looking for additional features and dedicated support, we also offer a premium version called AppKickstarter.

With AppKickstarter, you’ll have access to features such as authentication with Firebase, data storage with Firebase and SqlDelight, onboarding screens, and payment integration with RevenueCat.

Our team is dedicated to providing you with the support you need to build the best possible cross-platform app.

If you’re interested in learning more about AppKickstarter and how it can help you take your project to the next level, please visit our website to learn more and purchase your license today.

Upgrade with AppKickstarter

We believe that AppKickstarter is the ultimate tool for building high-quality cross-platform apps, and we’re confident that you’ll love using it as much as we do.

Introduction

Voyager is a new navigation library built specifically for Compose multiplatform, which is designed to make navigation in Compose-based apps simpler and more intuitive.

It offers a composable API that allows developers to create screens with minimal effort, and provides an easy-to-use navigation stack that supports push, pop, replace, and other common navigation operations.

With Voyager, developers can build complex, multi-screen apps with ease, and can take advantage of Compose’s powerful tooling and state management capabilities to create responsive and performant UIs.

Open source template

Before continuing, we made an open source template for Kotlin multiplatform and Compose Multiplatform. Navigation with Voyager is setup. But that’s not all : UI, DI, offline cache, multiplatform localization and resources. All of this is setup.

Open source template with voyager

This is a version of AppKickstarter with very limited features but yet useful setup.

Back to our guide.

Why it’s important for Compose multiplatform applications

Voyager offers a unified approach to navigation in a multiplatform Compose project.

By using Voyager in the shared module of the app, there’s no need to duplicate the navigation implementation for each platform, reducing the development effort and improving the code maintainability.

Additionally, Voyager offers seamless integration with Compose’s lifecycle and state management, making it a powerful and efficient solution for multiplatform navigation in Jetpack Compose.

Basic setup of Voyager

In your MainActivity (or any other activity that uses Jetpack Compose), use the Navigator composable to set up your app’s navigation:

class MainActivity : AppCompatActivity() {
  override fun onCreate(savedInstanceState: Bundle?) { 
    super.onCreate(savedInstanceState) 
    setContent { 
      Navigator(HomeScreen) 
    }
  }
}

Use the push method of the Navigator instance to navigate to a new screen. Here is an example of how you can navigate from the HomeScreen to the DetailScreen:

data class DetailScreen(private val itemId: Int) : Screen {
  @Composable override fun Content() {
    // Your detail screen content goes here 
  } 
}

class HomeScreen : Screen {
  @Composable override fun Content() { 
    val navigator = LocalNavigator.current 
    Button(
      onClick = { navigator.push(DetailScreen(itemId = 123)) }
    ) { 
      Text("View details") 
    } 
  } 
}

This code sets up the Navigator with the HomeScreen as the initial screen. The push method is used to navigate to the DetailScreen when the user taps the “View details” button. The LocalNavigator is used to get a reference to the current navigator instance.

Screen models, state management, coroutine and Koin integration

State management is a crucial part of any mobile app. Voyager’s StateScreenModel is a helpful tool for managing screen state and updating the UI based on changes to that state. The StateScreenModel class extends the ScreenModel interface and provides a mutable MutableState property that can be used to hold and update the current state of the screen.

To use the StateScreenModel in a Voyager screen, you can define a sealed class that represents the possible states for your screen. For example:

sealed class PersonScreenState { 
  object Init: PersonScreenState() 
  object Loading : PersonScreenState() 
  data class Content(val person: Person) : PersonScreenState() 
}

Then, define a StateScreenModel that takes the initial state as a parameter:

class PersonDetailScreenModel(private val repository: PersonRepository) : StateScreenModel<MyScreenState>(initialState) {
  fun getPerson(id: String) { 
    coroutineScope.launch { 
      mutableState.value = State.Loading 
      mutableState.value = State.Result(person = getPerson(id)) 
    }
  }
}

One of the benefits of using Voyager’s StateScreenModel is the reduction in code required for state management.

Since StateScreenModel integrates a mutable state, there’s no need to manually define a public StateFlow for the state and a private mutableStateFlow for the same state. This saves developers from writing a lot of template code and reduces the likelihood of errors.

Coroutines integration

As you can see in this code, Voyager integrates seamlessly with coroutines.

It makes it easy to manage asynchronous operations within screen models.

Screen models in Voyager come with a coroutineScope property that allows you to launch coroutines and ensure they are properly cancelled when the screen model is disposed.

The getPerson function launches a coroutine using coroutineScope.launch. This allows you to perform asynchronous operations such as network requests, database queries, and other time-consuming tasks without blocking the UI thread.

Integration of dependency injection with Koin

You can then use this StateScreenModel in your screen and update the state as needed, which will automatically trigger a recomposition of the UI.

In your screen, you can then inject the screen model using the getScreenModel() function from the koin voyager library. Here is an example:

class PersonDetailScreen(val id: String) : Screen { 
  @Composable override fun Content() { 
    val screenModel = getScreenModel<PersonDetailScreenModel>() { parametersOf(id) } 
    val state by screenModel.state.collectAsState() 
    when (state) { 
      is State.Loading -> LoadingContent() 
      is State.Result -> PersonContent(state.post)
    }
  }
}

Voyager integrates well with the Koin dependency injection framework. It allows for easy management and injection of dependencies into screen models. To use Koin with Voyager, you will need to include the Koin dependency in your project and define your Koin modules.

To define a Koin module for a screen model, you can use the factory component and provide a lambda that returns an instance of the screen model. Here is an example:

val homeModule = module { factory { HomeScreenModel() } }

Transition

Voyager comes with a built-in transition mechanism that allows you to create smooth transitions between screens.

To use the transition mechanism, you can pass a Transition instance to the Navigator composable.

The Transition instance defines how the transition should be animated.

For example, the following code shows how to use the SlideTransition to animate the transition between screens:

setContent { Navigator(HomeScreen) { navigator -> SlideTransition(navigator) } }

In this example, the SlideTransition is used to animate the transition between screens. The SlideTransition takes the navigator object as an argument, which is used to get the current screen and the previous screen. The SlideTransition then animates the transition between these screens by sliding the new screen in from the right.

There are several other built-in transitions available in Voyager, such as the FadeTransition and the ScaleTransition. You can also create your own custom transitions by implementing the Transition interface.

Why we love Voyager

At AppKickstarter, we love using Voyager because it is an efficient solution for implementing navigation in our Kotlin Multiplatform projects. Voyager solves the problem of duplicate navigation code in our Android and iOS projects by providing a shared navigation module.

Additionally, its integration with popular Kotlin tools such as Koin and Coroutines makes it easy to manage dependencies and asynchronous tasks.

Open Source template with Voyager

We offer a free and open source template for building multiplatform apps using Compose multiplatform and Voyager.

Our template includes everything you need to get started, from setting up Voyager to defining screens and injecting dependencies using Koin.

Upgrade Your Cross-Platform App Development with AppKickstarter

For those looking for additional features and dedicated support, we also offer a premium version called AppKickstarter.

With AppKickstarter, you’ll have access to features such as authentication with Firebase, data storage with Firebase and SqlDelight, onboarding screens, and payment integration with RevenueCat.

Our team is dedicated to providing you with the support you need to build the best possible cross-platform app.

If you’re interested in learning more about AppKickstarter and how it can help you take your project to the next level, please visit our website to learn more and purchase your license today.

Upgrade with AppKickstarter

We believe that AppKickstarter is the ultimate tool for building high-quality cross-platform apps, and we’re confident that you’ll love using it as much as we do.

Share on X
Share on LinkedIn
Share on Facebook

Free AppKickstarter version (save 1 day)

I'm passionate about making Kotlin and Compose Multiplatform resources widely accessible. That's why I've created a free template just for you. Claim yours now!

Get Free Template