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:
Routes - Type-safe navigation destinations using sealed classes
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
Route Characteristics
Route Type Parameters Serializable HomeData object None No ProductDetailData class productId: StringNo ErrorData object None Yes
The @Serializable annotation enables state restoration for the Error route across process death.
NavigationWrapper
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
backStack : Current navigation stack
onBack : Handler to remove last entry
entryProvider : Maps routes to composables
transitionSpec : Forward navigation animation
popTransitionSpec : Back navigation animation
predictivePopTransitionSpec : Predictive back gesture animation
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" )
}
}
Navigation Patterns
Forward Navigation
Back Navigation
Accessing Parameters
// 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
Forward Navigation
New screen slides in from right while current screen slides out to left
Back Navigation
Previous screen slides in from left while current screen slides out to right
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
}
Navigate to Product Detail
// 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
Compile-Time Verification
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)
}
Sealed Class Exhaustiveness
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
Navigation Flow Diagram
Screens Explore screen implementations
Architecture Understand the app structure
State Management Learn about UiState patterns