Skip to main content
TecMeli uses the Navigation3 library to implement type-safe, composable navigation with smooth animations and predictive back gestures.

Overview

The navigation system consists of two main components:
  1. Routes - Type-safe navigation destinations using sealed classes
  2. NavigationWrapper - Composable navigation host managing the back stack
Navigation3 provides compile-time safety for navigation arguments and supports modern Android navigation patterns.

Routes

Routes are defined as a sealed class hierarchy implementing NavKey, providing type-safe navigation destinations.

Implementation

import androidx.navigation3.runtime.NavKey
import kotlinx.serialization.Serializable

sealed class Routes: NavKey {
    data object Home : Routes()

    data class ProductDetail(val productId: String) : Routes()

    @Serializable
    data object Error : Routes()
}

Route Types

Home

Main screen with product search functionality

ProductDetail

Detail view with product ID parameter

Error

Error state screen

Route Characteristics

RouteTypeParametersSerializable
HomeData objectNoneNo
ProductDetailData classproductId: StringNo
ErrorData objectNoneYes
The @Serializable annotation enables state restoration for the Error route across process death.

The NavigationWrapper composable sets up the navigation host and manages the back stack.

Core Implementation

@Composable
fun NavigationWrapper() {
    val backStack = remember { mutableStateListOf<Routes>(Routes.Home) }

    NavDisplay(
        backStack = backStack,
        onBack = { backStack.removeLastOrNull() },
        entryProvider = appEntryProvider(backStack),
        transitionSpec = { slideIn() },
        popTransitionSpec = { slideOut() },
        predictivePopTransitionSpec = { slideOut() }
    )
}

Key Components

val backStack = remember { mutableStateListOf<Routes>(Routes.Home) }
  • Initialized with Routes.Home as the start destination
  • Uses mutableStateListOf for reactive updates
  • Automatically triggers recomposition on changes

Entry Provider

The entry provider maps route types to their corresponding screen composables.

Implementation

private fun appEntryProvider(backStack: MutableList<Routes>) = entryProvider {
    entry<Routes.Home> {
        HomeScreen(
            navigateToDetail = { id ->
                backStack.add(Routes.ProductDetail(id))
            }
        )
    }

    entry<Routes.ProductDetail> { key ->
        ProductDetailScreen(
            id = key.productId,
            onBack = { backStack.removeLastOrNull() }
        )
    }

    entry<Routes.Error> {
        Text("error")
    }
}
// Navigate to product detail
navigateToDetail = { id ->
    backStack.add(Routes.ProductDetail(id))
}

Animations

TecMeli implements smooth slide animations for navigation transitions.

Slide In Animation

Used for forward navigation (entering new screens):
private fun slideIn(): ContentTransform {
    return slideInHorizontally(
        initialOffsetX = { it },        // Start from right edge
        animationSpec = tween(250)      // 250ms duration
    ) togetherWith slideOutHorizontally(
        targetOffsetX = { -it },        // Exit to left edge
        animationSpec = tween(250)
    )
}

Slide Out Animation

Used for back navigation (returning to previous screens):
private fun slideOut(): ContentTransform {
    return slideInHorizontally(
        initialOffsetX = { -it },       // Enter from left edge
        animationSpec = tween(250)      // 250ms duration
    ) togetherWith slideOutHorizontally(
        targetOffsetX = { it },         // Exit to right edge
        animationSpec = tween(250)
    )
}

Animation Flow

1

Forward Navigation

New screen slides in from right while current screen slides out to left
2

Back Navigation

Previous screen slides in from left while current screen slides out to right
3

Predictive Back

Supports Android’s predictive back gesture with the same slide-out animation
The togetherWith operator ensures both animations run simultaneously for smooth transitions.

Usage Examples

Basic Navigation

@Composable
fun MyScreen() {
    NavigationWrapper()  // Sets up entire navigation system
}
// From HomeScreen
HomeScreen(
    navigateToDetail = { productId ->
        backStack.add(Routes.ProductDetail(productId))
    }
)

// Example usage
onItemClick = { productId ->
    navigateToDetail(productId)  // Type-safe navigation with parameter
}

Handle Back Navigation

// From ProductDetailScreen
ProductDetailScreen(
    id = key.productId,
    onBack = { 
        backStack.removeLastOrNull()  // Returns to previous screen
    }
)

// In TopAppBar
IconButton(onClick = onBack) {
    Icon(Icons.Default.ArrowBack, contentDescription = "Volver")
}

Advanced Features

Back Stack Manipulation

Add Route

backStack.add(Routes.ProductDetail(id))
Navigate forward to a new destination

Remove Last

backStack.removeLastOrNull()
Navigate back to previous screen

Clear Stack

backStack.clear()
backStack.add(Routes.Home)
Reset to home screen

Replace Current

backStack.removeLastOrNull()
backStack.add(newRoute)
Replace current destination

Type Safety Benefits

Routes are checked at compile time, preventing runtime navigation errors:
// This won't compile - productId is required
backStack.add(Routes.ProductDetail())

// This compiles correctly
backStack.add(Routes.ProductDetail("MLU123456789"))
Navigation arguments are type-safe and guaranteed to exist:
entry<Routes.ProductDetail> { key ->
    // key.productId is guaranteed to be a String
    ProductDetailScreen(id = key.productId)
}
When pattern matching on Routes, the compiler ensures all cases are handled:
when (route) {
    is Routes.Home -> { /* handled */ }
    is Routes.ProductDetail -> { /* handled */ }
    is Routes.Error -> { /* handled */ }
    // Compiler ensures no cases are missing
}

Best Practices

Single Source of Truth

The back stack is the single source of truth for navigation state

Immutable Routes

Use data classes for routes with parameters, data objects for parameterless routes

Callback Pattern

Pass navigation callbacks to screens rather than exposing the back stack

State Restoration

Use @Serializable for routes that need state restoration

Do’s and Don’ts

  • Define routes as sealed class members
  • Pass parameters as constructor arguments
  • Use data classes for routes with parameters
  • Provide clear navigation callbacks
  • Keep animations consistent across the app
  • Expose the back stack directly to screens
  • Hardcode navigation logic in composables
  • Mix navigation libraries
  • Skip animation specifications
  • Use nullable parameters when they’re required


Screens

Explore screen implementations

Architecture

Understand the app structure

State Management

Learn about UiState patterns

Build docs developers (and LLMs) love