Skip to content

roudikk/amper-compose-navigation-ksp

Repository files navigation

Navigation KSP Showcase in standalone Amper

This project demonstrates how to use Kotlin Symbol Processing (KSP) with Amper build system to generate type-safe navigation boilerplate code for Compose Desktop applications. It showcases a modular architecture with reusable module templates and code generation techniques.

This project is not production ready or meant to be used in an actual project, it's more of a proof of concept.

Project Overview

  1. KSP Processors in Amper: Using Kotlin Symbol Processing to generate code in an Amper-based project
  2. Module Templates: Creating reusable module templates for consistent feature implementation
  3. Compose Navigation Boilerplate Generation: Automating the creation of type-safe navigation code

Type-Safe Navigation

The project implements a type-safe navigation system using KSP to generate the boilerplate code. This provides:

  • Compile-time safety for navigation routes
  • Automatic parameter passing between screens
  • Centralized navigation destination registration
  • Parameters can be ignored in the declared keys

KSP Processors

Two main KSP processors are used:

  1. DestinationProcessor: Processes @Destination annotations to generate navigation code for individual screens
  2. NavBuilderProcessor: Aggregates all generated navigation destinations into a single function

Module Templates

The project uses Amper module templates to standardize feature implementation:

  • feature.module-template.yaml: Defines dependencies, settings, and KSP processors for feature modules
  • Ensures consistent configuration across all feature modules
  • Simplifies the creation of new features

Project Structure

  • app: Main application module with the entry point
  • navigation:
    • core: Core navigation interfaces and annotations
    • processor: KSP processor for generating navigation code
    • aggregator: KSP processor for aggregating all navigation destinations
  • feature: Feature modules (home, profile, settings)
  • components: Reusable UI components
  • template: Module templates for creating new features
  • external: A feature module placed outside /feature to test KSP processors

How It Works

  1. Feature screens are annotated with @Destination
  2. KSP processes these annotations during compilation
  3. Type-safe navigation code is generated for each destination, for each module
  4. All destinations for all modules are aggregated into a single function (allDestinations)
  5. The application uses this generated code for navigation

Example Usage

// Define a destination key
@Serializable
data object HomeKey : DestinationKey

// Annotate a composable as a destination
@Destination(HomeKey::class)
@Composable
fun HomeScreen() {
    // Screen implementation
}

@Serializable
data class ProfileKey(
    val id: String,
    @Destination.Ignore
    val optional: Boolean = false,
) : DestinationKey

// Annotate a composable as a destination
@Destination(ProfileKey::class)
@Composable
fun ProfileScreen(id: String) { // must match name in key
    // Screen implementation
}

A generated allDestinations() function will be generated which can then be used in the NavHost Composable:

@Composable
fun App() { 
    val navController: NavHostController = rememberNavController()
    
    NavHost(
        navController = navController,
        startDestination = HomeKey,
        builder = { allDestinations() },
    )
}

Alternatively, each module that has the KSP processor applied will also generate a ${moduleName}Destinations

@Composable
fun App() { 
    val navController: NavHostController = rememberNavController()
    
    NavHost(
        navController = navController,
        startDestination = HomeKey,
        builder = { 
            homeDestinations()
            profileDestinations()
        },
    )
}

Run the project

  1. Clone the repository
  2. Run the application using Amper:
    ./amper run
    

TODOs

  • Instead of feature modules depending on each other to navigate through module.yaml, screens should have a lambda param (For ex: onNavigateToProfile: () -> Unit), the processor would add the same parameter to the generated ${moduleName}Destinations so the linking can happen in app module, similar to recommended compose navigation usage.

About

Standalone Amper with KSP Processor to generate boilerplate for Type-Safe Compose Navigation

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published