Jetpack Navigation Compose 2026: Navigasi Type-Safe dan Pertanyaan Interview

Pelajari Jetpack Navigation Compose 2.10 dengan navigasi type-safe menggunakan Kotlin Serialization. Panduan lengkap dengan contoh kode dan pertanyaan interview Android.

Jetpack Navigation Compose 2026: Navigasi Type-Safe dan Pertanyaan Interview

Jetpack Navigation Compose 2.10 menghadirkan keamanan tipe waktu kompilasi untuk navigasi Android melalui Kotlin Serialization. Pendekatan ini menghilangkan crash runtime akibat ketidakcocokan argumen dan menyederhanakan refactoring di seluruh codebase yang besar.

Navigation 2.10 Stabil

Navigation Compose 2.10.0, dirilis Agustus 2026, memerlukan minimum SDK 24. Route type-safe menggunakan data class @Serializable sebagai pengganti destinasi berbasis string, sehingga menangkap error navigasi pada waktu kompilasi.

Menyiapkan Navigasi Type-Safe di Navigation 2.10

API navigasi type-safe memerlukan Kotlin Serialization. Tambahkan dependensi berikut ke build.gradle.kts level modul:

build.gradle.ktskotlin
plugins {
    id("org.jetbrains.kotlin.plugin.serialization") version "2.1.0"
}

dependencies {
    implementation("androidx.navigation:navigation-compose:2.10.0")
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
}

Definisikan destinasi sebagai tipe serializable. Gunakan object untuk layar tanpa argumen dan data class untuk layar yang menerima parameter:

Routes.ktkotlin
import kotlinx.serialization.Serializable

@Serializable
object Home

@Serializable
object ProductList

@Serializable
data class ProductDetail(val productId: String)

@Serializable
data class Checkout(val cartId: String, val promoCode: String? = null)

Kompiler memastikan bahwa semua class route dianotasi dengan @Serializable. Anotasi yang hilang memicu error lint di Navigation 2.9+.

Membangun NavHost dengan Route Serializable

Composable NavHost menerima tipe serializable secara langsung. Setiap blok composable<T> menangani tipe destinasi tertentu:

AppNavigation.ktkotlin
@Composable
fun AppNavigation(
    navController: NavHostController = rememberNavController()
) {
    NavHost(
        navController = navController,
        startDestination = Home
    ) {
        composable<Home> {
            HomeScreen(
                onNavigateToProducts = { navController.navigate(ProductList) }
            )
        }

        composable<ProductList> {
            ProductListScreen(
                onProductClick = { productId ->
                    navController.navigate(ProductDetail(productId))
                }
            )
        }

        composable<ProductDetail> { backStackEntry ->
            // Ekstrak route bertipe dari back stack entry
            val route: ProductDetail = backStackEntry.toRoute()
            ProductDetailScreen(
                productId = route.productId,
                onCheckout = { cartId ->
                    navController.navigate(Checkout(cartId))
                }
            )
        }

        composable<Checkout> { backStackEntry ->
            val route: Checkout = backStackEntry.toRoute()
            CheckoutScreen(
                cartId = route.cartId,
                promoCode = route.promoCode
            )
        }
    }
}

Extension toRoute<T>() mengembalikan instance destinasi yang sepenuhnya bertipe. Mengganti nama properti di data class secara otomatis memperbarui semua penggunaan melalui refactoring IDE.

Nested Navigation Graph untuk Modul Fitur

Aplikasi besar mendapat manfaat dari nested navigation graph. Setiap modul fitur mendefinisikan graph-nya sendiri, dan root graph menyusunnya:

AuthNavigation.ktkotlin
@Serializable
object AuthGraph  // Identifier graph

@Serializable
object Login

@Serializable
object Register

@Serializable
data class PasswordReset(val email: String)

fun NavGraphBuilder.authNavGraph(navController: NavHostController) {
    navigation<AuthGraph>(startDestination = Login) {
        composable<Login> {
            LoginScreen(
                onRegisterClick = { navController.navigate(Register) },
                onForgotPassword = { email ->
                    navController.navigate(PasswordReset(email))
                },
                onLoginSuccess = {
                    navController.navigate(Home) {
                        popUpTo<AuthGraph> { inclusive = true }
                    }
                }
            )
        }

        composable<Register> {
            RegisterScreen(
                onRegistrationComplete = {
                    navController.navigate(Home) {
                        popUpTo<AuthGraph> { inclusive = true }
                    }
                }
            )
        }

        composable<PasswordReset> { backStackEntry ->
            val route: PasswordReset = backStackEntry.toRoute()
            PasswordResetScreen(email = route.email)
        }
    }
}

popUpTo<AuthGraph> dengan inclusive = true menghapus seluruh alur auth dari back stack setelah login berhasil, mencegah pengguna kembali ke layar login.

Siap menguasai wawancara Android Anda?

Berlatih dengan simulator interaktif, flashcards, dan tes teknis kami.

Value Class sebagai Argumen Route

Navigation 2.9.0-alpha03 menambahkan dukungan untuk value class dalam route. Pola ini memaksakan keamanan tipe untuk ID dan mencegah tercampurnya parameter dengan tipe primitif yang sama:

Domain.ktkotlin
@JvmInline
@Serializable
value class ProductId(val value: String)

@JvmInline
@Serializable
value class UserId(val value: String)

@Serializable
data class ProductDetail(val productId: ProductId)

@Serializable
data class UserProfile(val userId: UserId)

Sekarang kompiler mencegah pengiriman UserId di tempat yang mengharapkan ProductId, bug umum dalam navigasi berbasis string.

Deep link memetakan URI eksternal ke route serializable. Definisikan pola di blok composable:

AppNavigation.ktkotlin
composable<ProductDetail>(
    deepLinks = listOf(
        navDeepLink {
            uriPattern = "https://example.com/products/{productId}"
        }
    )
) { backStackEntry ->
    val route: ProductDetail = backStackEntry.toRoute()
    ProductDetailScreen(productId = route.productId)
}

Placeholder {productId} dipetakan langsung ke properti productId di data class ProductDetail. Navigation mengekstrak dan mendeserialize argumen secara otomatis.

Daftarkan deep link di AndroidManifest.xml:

xml
<!-- AndroidManifest.xml -->
<activity android:name=".MainActivity">
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data
            android:scheme="https"
            android:host="example.com"
            android:pathPrefix="/products" />
    </intent-filter>
</activity>

Predictive Back Gesture dengan Navigation Compose

Android 14+ mendukung predictive back gesture di mana pengguna melihat pratinjau layar sebelumnya sebelum menyelesaikan aksi. Navigation Compose 2.10 terintegrasi dengan fitur ini melalui SeekableTransitionState:

AppNavigation.ktkotlin
NavHost(
    navController = navController,
    startDestination = Home,
    enterTransition = { slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.Start) },
    exitTransition = { slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.Start) },
    popEnterTransition = { slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.End) },
    popExitTransition = { slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.End) }
) {
    // destinasi
}

Predictive back gesture menampilkan destinasi sebelumnya secara real-time saat pengguna melakukan swipe. Melepaskan gesture sebelum threshold membatalkan navigasi dan kembali ke layar saat ini.

Pertanyaan Interview Umum tentang Navigation Compose

Interview Android senior sering menggali keputusan arsitektur navigasi. Berikut adalah pertanyaan yang membedakan kandidat berpengalaman.

Mengapa menggunakan navigasi type-safe daripada route berbasis string?

Route berbasis string seperti "product/{productId}" menyebabkan crash runtime ketika argumen salah eja atau memiliki tipe yang salah. Route type-safe menangkap error ini pada waktu kompilasi. Refactoring parameter route memperbarui semua penggunaan melalui tooling IDE, sedangkan route berbasis string memerlukan find-and-replace manual di seluruh codebase.

Bagaimana cara berbagi data antar destinasi tanpa mengirim objek besar?

Hindari Argumen Route Besar

Route diserialize ke dalam saved instance state. Mengirim objek besar seperti katalog produk berisiko TransactionTooLargeException. Kirim ID dan muat data dari repository atau shared ViewModel sebagai gantinya.

Pola untuk berbagi data melibatkan scoped ViewModel:

ProductViewModel.ktkotlin
@HiltViewModel
class ProductViewModel @Inject constructor(
    private val repository: ProductRepository,
    savedStateHandle: SavedStateHandle
) : ViewModel() {
    // Ekstrak route bertipe dari SavedStateHandle
    private val productId: String = savedStateHandle.toRoute<ProductDetail>().productId

    val product = repository.getProduct(productId)
        .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), null)
}

Bagaimana cara menguji navigasi di Compose?

Pengujian navigasi memverifikasi bahwa aksi pengguna memicu perubahan destinasi yang benar:

NavigationTest.ktkotlin
class NavigationTest {
    @get:Rule
    val composeTestRule = createComposeRule()

    @Test
    fun clickingProduct_navigatesToDetail() {
        val navController = TestNavHostController(ApplicationProvider.getApplicationContext())
        navController.navigatorProvider.addNavigator(ComposeNavigator())

        composeTestRule.setContent {
            AppNavigation(navController = navController)
        }

        // Navigasi ke daftar produk
        composeTestRule.onNodeWithText("View Products").performClick()

        // Klik produk pertama
        composeTestRule.onNodeWithTag("product_item_0").performClick()

        // Verifikasi destinasi saat ini
        val currentRoute = navController.currentBackStackEntry?.toRoute<ProductDetail>()
        assertThat(currentRoute).isNotNull()
    }
}

Apa yang terjadi ketika NavController digunakan setelah dihancurkan?

Navigation 2.9.0-alpha01 mengubah perilaku ini. Menggunakan NavController setelah Activity atau Fragment yang menghostingnya dihancurkan sekarang melempar IllegalStateException. Versi sebelumnya gagal secara diam-diam atau menghasilkan perilaku yang tidak terdefinisi. Perubahan ini memunculkan bug lifecycle lebih awal dalam pengembangan.

Bagaimana cara menangani navigasi kondisional berdasarkan status auth?

Pola umum mengamati status auth dan mengarahkan sesuai:

MainScreen.ktkotlin
@Composable
fun MainScreen(
    authState: AuthState,
    navController: NavHostController = rememberNavController()
) {
    LaunchedEffect(authState) {
        when (authState) {
            is AuthState.Authenticated -> {
                navController.navigate(Home) {
                    popUpTo(0) { inclusive = true }
                }
            }
            is AuthState.Unauthenticated -> {
                navController.navigate(AuthGraph) {
                    popUpTo(0) { inclusive = true }
                }
            }
            else -> { /* loading, tidak melakukan apa-apa */ }
        }
    }

    NavHost(
        navController = navController,
        startDestination = if (authState is AuthState.Authenticated) Home else AuthGraph
    ) {
        authNavGraph(navController)
        composable<Home> { HomeScreen() }
    }
}

LaunchedEffect bereaksi terhadap perubahan status auth dan menghapus seluruh back stack dengan popUpTo(0) { inclusive = true } untuk mencegah navigasi kembali ke alur yang salah.

Mulai berlatih!

Uji pengetahuan Anda dengan simulator wawancara dan tes teknis kami.

Migrasi dari Route String ke Navigasi Type-Safe

Proyek yang sudah ada dapat bermigrasi secara bertahap. Route string dan route type-safe dapat berdampingan dalam NavHost yang sama:

AppNavigation.ktkotlin
NavHost(
    navController = navController,
    startDestination = "home"  // Route string legacy
) {
    // Destinasi berbasis string legacy
    composable("home") {
        HomeScreen(onNavigateToProduct = { id ->
            navController.navigate(ProductDetail(id))  // Navigasi type-safe baru
        })
    }

    // Destinasi type-safe baru
    composable<ProductDetail> { backStackEntry ->
        val route: ProductDetail = backStackEntry.toRoute()
        ProductDetailScreen(productId = route.productId)
    }
}

Pendekatan ini memungkinkan migrasi satu layar pada satu waktu tanpa menulis ulang seluruh navigation graph.

Poin Penting Jetpack Navigation Compose di 2026

  • Navigation 2.10.0 adalah versi stabil saat ini dengan minimum SDK 24
  • Route type-safe menggunakan data class @Serializable sebagai pengganti destinasi string
  • Value class memberikan keamanan tipe tambahan untuk ID dan mencegah tercampurnya parameter
  • Nested navigation graph mengorganisir modul fitur dengan back stack independen
  • Deep link memetakan pola URI langsung ke properti route
  • Predictive back gesture terintegrasi secara otomatis dengan transisi standar
  • Argumen route harus tetap ringan: kirim ID, muat data dari repository
  • Migrasi dari route string dapat dilakukan secara bertahap dengan navigation graph campuran
  • Persiapan interview Android membahas pola-pola ini secara mendalam
  • Lihat juga pertanyaan interview Jetpack Compose untuk konsep UI terkait
Tantangan harian

Bisakah kamu menemukan bug di Android?

Satu potongan kode nyata, satu bug tersembunyi, satu percobaan per hari. Tanpa akun untuk mencoba.

Anthony Fillion-Maillet

Ditulis oleh

Anthony Fillion-Maillet

Pendiri SharpSkill

Developer fullstack selama lebih dari 10 tahun. Ia menjalankan SharpSkill dan bertanggung jawab atas semua yang diterbitkan di sini.

Diperbarui 1 September 2026

Bagikan

Artikel terkait