Project Url: FunkyMuse/foSho
Introduction: Type-safe navigation library for Jetpack Compose
More: Author   ReportBugs   OfficialWebsite   

"Opinionated" Android type safe navigation for Compose's Navigation Component

A KSP library that helps you generate type safe code with minimal effort and provides out of the box solutions to bundle everything together and scale your app in easy manner.

Main features

  • Typesafe navigation arguments (special thanks for parsing the arguments to compose destinations)
  • Simple setup with minimal code
  • ViewModel type safe arguments with injection support when using Hilt
  • Screen "level" type safe arguments
  • Callback arguments
  • Bottom sheet support through Accompanist Material
  • Nested navigation support out of the box
  • Transitions enter/exit to and from screens
  • Deep links
  • Graph aggregator factory
  • Custom navigator to open screens easily
  • Global navigation
  • Multi (out of the box) and single module support

Everything you can do with the Official Jetpack Compose Navigation but in a type safe way.


The library is available through JitPack.

  1. Add JitPack to your project's settings.gradle

    dependencyResolutionManagement {
     repositories {
         maven { setUrl("") }
  2. Add the dependency in the build.gradle ```toml [versions] foSho = ksp =

[libraries] foSho-android = { module = "com.github.FunkyMuse.foSho:navigator-android", version.ref = "foSho" } foSho-codegen = { module = "com.github.FunkyMuse.foSho:navigator-codegen", version.ref = "foSho" }

[plugins] ksp = { id = "", version.ref = "ksp" }

Inside the project build.gradle make sure to add the [KSP]( plugin
plugins {

Inside the :app module

plugins {
dependencies {

If you intend to use Hilt/Dagger/Anvil with foSho and want your generated ViewModel arguments injectable, inside :app module set foSho.injectViewModelArguments to "true", default is "false"

ksp {
    arg("foSho.injectViewModelArguments", "true")
Single module Inside the :app module make sure to add the KSP argument, the library is multi module by default. kotlin ksp { arg("foSho.singleModule", "true") }
  1. Make magic happen

There are three things you need to write code for and it's pretty natural

  1. Graph
  2. Destination (Argument, Callback Argument)
  3. Content

All you need to do is click build or just run ./gradlew kspDebugKotlin as a faster way to get the generated code.

  • Graph: Starting point is a @Graph, each Graph has a startingDestination and other destinations, also a Graph can be a rootGraph (only one throughout your app).
  • Destination: every destination that's part of startingDestination and destinations is marked with @Destination and you implement one of the three UI types Screen, Dialog for which you can control the properties) and BottomSheet for each one you can additionally control whether to generate view model arguments or nav stack entry arguments, val generateViewModelArguments: Boolean = false, val generateScreenEntryArguments: Boolean = false, you can annotate a destination with @Argument and @CallbackArgument in order to control to/from arguments.
  • Content: everytime you click build a Destination implementation is generated for you, it's your responsibility to implement it and annotate that object with a @Content In code this will look like this
    startingDestination = Home::class,
    destinations = [HomeDetails::class],
    rootGraph = true
internal object HomeGraph

    name = "hideBottomNav",
    argumentType = ArgumentType.BOOLEAN,
    defaultValue = DefaultBooleanValueFalse::class
internal object Home : Screen

@Destination(generateViewModelArguments = true, generateScreenEntryArguments = false)
@Argument(name = "cardId", argumentType = ArgumentType.INT)
@CallbackArgument(name = "clickedShare", argumentType = ArgumentType.BOOLEAN)
internal object HomeDetails : Screen {
    override val deepLinksList: List<DeepLinkContract>
        get() = listOf(
                action = Intent.ACTION_VIEW,
                uriPattern = "custom:url/{cardId}"

internal object HomeContent : HomeDestination {
  override fun AnimatedContentScope.Content() {
    val argumentsFromHomeDetailsDestination = HomeDetailsCallbackArguments.rememberHomeDetailsCallbackArguments()
      key = HomeDetailsCallbackArguments.CLICKED_SHARE,
      onValue = {
        if (it == true){
          //user has clicked share
    HomeScreen(onClick = {
      Navigator.navigateSingleTop(HomeDetailsDestination.openHomeDetails(cardId = 42))

internal object HomeDetailsContent : HomeDetailsDestination {
  override fun AnimatedContentScope.Content() {


A GraphFactory is generated for you which you can use with an extension function addGraphs to have ease of use like

  navigationGraphs = GraphFactory.graphs

you can checkout this line.

A Navigator is there for you to collect the navigation events and also to send navigation events, for a single module setup, you can check out the sample.

Multi module You would need to create one umbrella module, usually named "navigator" or however you see fit where you would write the code for the Graphs kotlin //:navigator module //UserAccountGraph.kt @Graph( startingDestination = UserAccountDetails::class, destinations = [ EditAccountDetails::class, DeleteAccount::class, ChangePassword::class, ] ) internal object UserAccountGraph @Destination internal object UserAccountDetails : Screen @Destination internal object ChangePassword : BottomSheet @Destination internal object DeleteAccount : Dialog @Destination(generateScreenEntryArguments = true) @Argument(name = "email", argumentType = ArgumentType.STRING) @CallbackArgument(name = "isAccountUpdated", argumentType = ArgumentType.BOOLEAN) internal object EditAccountDetails : Screen //HomeGraph.kt @Graph(startingDestination = Home::class, rootGraph = true) internal object HomeGraph @Destination @Argument(name = "hideBottomNav", argumentType = ArgumentType.BOOLEAN, defaultValue = DefaultBooleanValueFalse::class) internal object Home : Screen Your :navigator module acts as the only point where you have the navigation codegen code and nothing else, here you can control the arg whether to generate injectable view model arguments through kotlin ksp { arg("foSho.injectViewModelArguments", "true") } Then inside your feature module kotlin // :feature:user_details @Content internal object UserAccountDetailsContent : UserAccountDetailsDestination { @Composable override fun AnimatedContentScope.Content() { } } and also make sure to add the :feature:user_details in your :app module so that it can be aggregated into the GraphFactory.

Important for Kotlin < 1.8.0

When using Kotlin version older than 1.8.0, you need to make sure the IDE looks at the generated folder. See KSP related issue.

How to do it depends on the AGP version you are using in this case:

Warning: In both cases, add this inside android block and replacing applicationVariants with libraryVariants if the module is not an application one (i.e, it uses '' plugin).

Since AGP (Android Gradle Plugin) version 7.4.0
groovy - build.gradle(:module-name) gradle applicationVariants.all { variant -> variant.addJavaSourceFoldersToModel( new File(buildDir, "generated/ksp/${}/kotlin") ) } kotlin - build.gradle.kts(:module-name) gradle applicationVariants.all { addJavaSourceFoldersToModel( File(buildDir, "generated/ksp/$name/kotlin") ) }
For AGP (Android Gradle Plugin) version older than 7.4.0
groovy - build.gradle(:module-name) gradle applicationVariants.all { variant -> kotlin.sourceSets { getByName( { kotlin.srcDir("build/generated/ksp/${}/kotlin") } } } kotlin - build.gradle.kts(:module-name) gradle applicationVariants.all { kotlin.sourceSets { getByName(name) { kotlin.srcDir("build/generated/ksp/$name/kotlin") } } }

Found this repository useful? ❤️

Support it by joining stargazers for this repository. 🌠

And follow me or check out my blog for my next creations! ⭐


Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.


Apache 2.0

About Me
GitHub: Trinea
Facebook: Dev Tools