SkillAgentSearch skills...

Kompass

Compose Multiplatform Navigation

Install / Use

/learn @3xcool/Kompass
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

Kompass (KMP Navigation)

Android Weekly Maven Central License Kotlin Compose API

<p align="center"> <strong>Modern navigation library for Compose Multiplatform (Android, iOS and Desktop).</strong><br/> Designed to make navigation predictable, scalable, and platform-agnostic. Built around pure reducers and reactive state updates, it replaces traditional navigation patterns with a more composable and testable approach. </p> <p align="center"> <img src="assets/KompassOverview.png" alt="Kompass Overview" /> </p>

▶ Watch usage overview

About

Kompass is the next-generation navigation library designed from the ground up for Jetpack Compose. Unlike traditional navigation approaches, Kompass embraces functional programming principles and reactive architecture patterns:

  • Pure State Management - Navigation state is immutable, serializable, and completely decoupled from UI logic. The entire navigation system is built on predictable, deterministic state transitions.
  • Reducer Pattern - All navigation rules are side-effect free, making the navigation core trivial to test without mocking frameworks or instrumentation.
  • Multi-Graph Architecture - Organize large applications across multiple modular navigation graphs with independent layouts and transitions.
  • Lifecycle-Aware Scopes - Built-in scope management provides ViewModel-like instance storage with automatic cleanup and memory leak prevention.
  • Deep Linking Made Simple - Extensible deep link handlers convert URIs into navigation commands with type-safe argument parsing.
  • Result - Deliver typed results between destinations without tight coupling or callback hell.
  • Persistent State - Automatic serialization and restoration across configuration changes, process death, and app relaunches.
  • Customizable Layouts & Transitions - Per-graph scene layouts support any composition pattern: single-stack, master-detail, split-screen, or custom multi-pane designs.

Perfect for applications that need robust, scalable, and testable navigation without the complexity of over-engineered frameworks.

Contents

Key Features

  • Pure State Management - Navigation state is immutable and serializable, separate from UI logic
  • Testable Reducer Pattern - All navigation rules are deterministic and side-effect free
  • Pluggable Layouts & Transitions - Customize screen transitions and multi-pane layouts per graph
  • Multi-Graph Support - Organize destinations across multiple navigation graphs
  • Navigation Scopes - Lifecycle-aware instance management (ViewModel-like) with automatic cleanup
  • Deep Linking - Built-in deep link support with extensible handlers
  • Result Passing - Deliver navigation results between destinations
  • Persistent State - Automatic serialization and restoration across configuration changes

Architecture

Kompass follows a clean separation of concerns:

Navigation Logic
    ↓
NavigationHandler (pure reducer: State + Command → State)
    ↓
NavigationState (immutable back stack)
    ↓
NavController (facade & effects)
    ↓
KompassNavigationHost (renders via NavigationGraph)
    ↓
Screen Content

Core Components

  • NavigationState - Immutable representation of the back stack
  • NavigationHandler - Pure reducer applying navigation commands
  • NavController - Public API for performing navigation
  • KompassNavigationHost - Root composable orchestrating rendering
  • NavigationGraph - Maps destinations to UI content
  • NavigationScopes - Thread-safe lifecycle-aware instance storage
  • BackStackEntry - Represents a single stack entry with destination, args, and scope

Installation

Add to your build.gradle.kts:

repositories {
    mavenCentral()
}

dependencies {
    implementation("com.tekmoon:kompass:1.0.0")
    implementation("org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.7")
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2")
}

Quick Start

1. Define Destinations

sealed interface MainDestination : Destination {
    data object Home : MainDestination {
        override val id: String = "home"
    }
    data object Profile : MainDestination {
        override val id: String = "profile"
    }
    data object Settings : MainDestination {
        override val id: String = "settings"
    }
}

2. Create a Navigation Graph

class MainNavigationGraph : NavigationGraph {
    override fun canResolveDestination(destinationId: String): Boolean =
        destinationId in setOf("home", "profile", "settings")

    override fun resolveDestination(
        destinationId: String,
        args: String?
    ): Destination = when (destinationId) {
        "home" -> MainDestination.Home
        "profile" -> MainDestination.Profile
        "settings" -> MainDestination.Settings
        else -> error("Unknown destination: $destinationId")
    }

    @Composable
    override fun Content(
        entry: BackStackEntry,
        destination: Destination,
        navController: NavController
    ) {
        when (destination) {
            is MainDestination.Home -> HomeScreen(navController)
            is MainDestination.Profile -> ProfileScreen(navController)
            is MainDestination.Settings -> SettingsScreen(navController)
        }
    }
}

3. Setup Navigation Host

@Composable
fun AppNavigation() {
    val navController = rememberNavController(
        startDestination = MainDestination.Home
    )

    KompassNavigationHost(
        navController = navController,
        graphs = persistentListOf(MainNavigationGraph())
    )
}

4. Navigate from Screens

@Composable
fun HomeScreen(navController: NavController) {
    Button(
        onClick = {
            navController.navigate(
                entry = BackStackEntry(
                    destinationId = "profile",
                    scopeId = newScope()
                )
            )
        }
    ) {
        Text("Go to Profile")
    }
}

Navigation Commands

Navigate

Push a new destination onto the back stack:

navController.navigate(
    entry = BackStackEntry(
        destinationId = "profile",
        args = """{"userId":"123"}""",
        scopeId = newScope()
    ),
    clearBackStack = false,        // Clear entire stack
    popUpTo = "home",              // Pop up to destination
    popUpToInclusive = false,      // Include the destination in pop
    reuseIfExists = false          // Reuse existing entry
)

Pop

Remove one or more entries from the back stack:

// Pop single entry
navController.pop()

// Pop with result
navController.pop(result = ProfileResult(userId = "123"))

// Pop multiple entries
navController.pop(count = 2)

// Pop until destination
navController.pop(popUntil = "home")

Replace Root

Replace the entire back stack with a single entry:

navController.replaceRoot(
    entry = BackStackEntry(
        destinationId = "home",
        scopeId = newScope()
    )
)

Navigation Scopes

Navigation Scopes provide lifecycle-aware instance storage similar to ViewModels:

@Composable
fun ProfileScreen(navController: NavController, entry: BackStackEntry) {
    val viewModel = rememberScoped<ProfileViewModel>(
        scopeId = entry.scopeId,
        factory = { ProfileViewModel() },
        onCleared = { it.close() }
    )

    // ViewModel survives recomposition but is cleared when entry is popped
    LaunchedEffect(Unit) {
        viewModel.loadProfile()
    }
}

Scope Types

Default Scope - Shared state across multiple navigations to same destination:

val entry = BackStackEntry(
    destinationId = "profile",
    scopeId = destination.defaultScope()  // "entry:profile"
)

Unique Scope - Isolated state for each navigation:

val entry = BackStackEntry(
    destinationId = "profile",
    scopeId = newScope()  // "entry:{randomUUID}"
)

Navigation Results

Pass data between destinations using results:

// Send result when popping
navController.pop(
    result = ProfileResult(userId = "123"),
    count = 1
)

// Receive result in destination
@Composable
fun HomeScreen(navController: NavController, entry: BackStackEntry) {
    val result = entry.results["profile_result"] as? ProfileResult

    LaunchedEffect(result) {
        if (result != null) {
            // Handle result
        }
    }
}

Custom Layouts & Transitions

Customize screen transitions and multi-pane layouts:

class MainNavigationGraph :

Related Skills

View on GitHub
GitHub Stars15
CategoryDevelopment
Updated6d ago
Forks0

Languages

Kotlin

Security Score

80/100

Audited on Mar 29, 2026

No findings