composable(): wat is het, NavHost en routering in Jetpack Compose

Auteur: IT Sectr Gepubliceerd: 2026-06-30 Leestijd: 9 min

composable() — is een functie van de Navigation Compose-bibliotheek die een scherm registreert in NavHost en een URL-route koppelt aan een Compose-layout. Wanneer navigatie naar een bepaalde route gaat, roept Jetpack Compose de bijbehorende composable-functie aan en geeft deze weer als het huidige scherm. In tegenstelling tot FragmentManager of Intent-gebaseerde navigatie werkt composable() op het niveau van één Activity en wordt volledig beheerd via Kotlin DSL. Volgens gegevens van Android Developers (2025) gebruikt meer dan 73% van de moderne Android-applicaties die op Jetpack Compose zijn gebouwd, specifiek Navigation Compose voor het organiseren van schermwisselingen.

Belangrijkste punten

  • composable() — functie voor schermregistratie in NavHost van de Navigation Compose-bibliotheek.
  • Route — elk scherm wordt geïdentificeerd door een tekstuele route die als eerste argument wordt doorgegeven.
  • Parameters — composable() ondersteunt argumenten via NavArgument, inclusief verplichte en optionele.
  • Nesting — geneste navigatie wordt ondersteund via geneste NavHosts met aparte routegrafen.
  • Prestaties — composable() gebruikt lazy-initialisatie: het scherm wordt alleen bij de eerste overgang aangemaakt.

Wat is composable() in NavHost

composable() — is een extensiefunctie (extension function) van het NavHost-object. Kotlin DSL maakt het mogelijk om deze binnen een NavHost-blok aan te roepen voor het declaratief beschrijven van alle schermen van de applicatie. Elke aanroep creëert een entry in de navigatiegraaf, waarbij een tekstuele route aan een composable-functie wordt gekoppeld. Wanneer de gebruiker naar een bepaalde route navigeert, toont NavHost de bijbehorende composable als het huidige scherm, waarbij het vorige scherm wordt verborgen.

De Navigation Compose-bibliotheek werd in 2021 door Google geïntroduceerd als alternatief voor Fragment-gebaseerde navigatie voor Jetpack Compose. Het belangrijkste voordeel — volledige compatibiliteit met het Compose-paradigma: composable() werkt in dezelfde levenscyclus als andere Compose-componenten, zonder dat FragmentManager of transacties nodig zijn. Dit elimineert een klasse fouten die verband houden met het niet overeenkomen van de Fragment- en Compose-levenscyclus.

Elke composable() ontvangt een tekstuele route en een lambda-functie die een NavBackStackEntry-object krijgt en een Composable UI retourneert. Binnen de lambda kan NavController worden benaderd via de aanroep navController uit de scope, wat het mogelijk maakt om overgangen naar andere schermen te organiseren. Deze architectuur maakt navigatie expliciet en voorspelbaar.

kotlin
@Composable
fun AppNavigation() {
    val navController = rememberNavController()
    
    NavHost(
        navController = navController,
        startDestination = "home"
    ) {
        composable("home") {
            HomeScreen(
                onNavigateToProfile = {
                    navController.navigate("profile")
                }
            )
        }
        composable("profile") {
            ProfileScreen(
                onBack = { navController.popBackStack() }
            )
        }
    }
}

Hoe werkt composable(): sleutels en parameters

Elke composable()-aanroep creëert in de interne graaf van NavHost een knooppunt met een unieke route-identificator. Wanneer NavController navigate() uitvoert, vergelijkt de bibliotheek de gevraagde route met alle geregistreerde composable-knooppunten en vindt de overeenkomende. Na een match wordt een NavBackStackEntry aangemaakt die op de navigatiestapel wordt geplaatst en de UI-compositie wordt gestart.

De interne implementatie van composable() maakt gebruik van het lazy-initialisatie-mechanisme: de compositie van het scherm vindt pas plaats op het moment van de eerste overgang naar deze route. Dit betekent dat schermen waar de gebruiker nooit naartoe is genavigeerd, geen geheugen innemen en geen code uitvoeren. Deze benadering verbetert de prestaties van applicaties met een groot aantal schermen aanzienlijk.

De parameter key in composable() maakt het mogelijk om het opnieuw aanmaken van het scherm te beheren. Standaard wordt composable niet opnieuw aangemaakt bij een herhaalde overgang naar dezelfde route — NavHost gebruikt de bestaande back stack entry. Als key echter wordt doorgegeven en verandert, maakt NavHost een nieuwe instantie van de composable-functie. Dit is nuttig voor schermen met dynamische gegevens waarbij de status bij heropenen geforceerd moet worden vernieuwd.

kotlin
val NavGraphBuilder.Composable: Unit
    get() = composable(
        route = "details/{itemId}",
        arguments = listOf(
            NavArgument("itemId") { 
                type = NavType.IntType
            }
        ),
        deepLinks = listOf(
            navDeepLink { uriPattern = "myapp://details/{itemId}" }
        )
    ) { backStackEntry ->
        val itemId = backStackEntry.arguments?.getInt("itemId") ?: 0
        DetailsScreen(itemId = itemId)
    }

Argumenten doorgeven via composable()

composable() ondersteunt een flexibel argumentsysteem via de parameter arguments. Elk argument wordt beschreven door een NavArgument-object dat het type, de standaardwaarde en de verplichting definieert. Argumenten worden in de route doorgegeven als padparameters (via accolades) of queryparameters (via vraagteken).

Padparameters worden direct in het routesjabloon aangegeven: "profile/{userId}". Bij navigatie naar de route "profile/42" haalt NavHost automatisch de waarde 42 op en maakt deze beschikbaar via backStackEntry.arguments. Queryparameters worden na het vraagteken toegevoegd: "search?query={text}" en worden ook automatisch door de bibliotheek geparseerd.

Bij het ophalen van argumenten is het belangrijk om de verplichting van de parameter te controleren via NavType.isNullableAllowed en standaardwaarden te verstrekken via NavArgument defaultValue. Als een verplichte parameter ontbreekt, genereert Navigation Compose een IllegalArgumentException, wat onzichtbare bugs met onjuiste routes voorkomt.

ArgumenttypeNavTypeVoorbeeld in route
IntNavType.IntType"item/{id}"
StringNavType.StringType"user/{name}"
BooleanNavType.BoolType"filter?enabled={value}"
FloatNavType.FloatType"map/{lat}/{lon}"
LongNavType.LongType"article/{timestamp}"

Voor het doorgeven van complexe objecten wordt aanbevolen om NavType.ParcelableType of NavType.SerializableType te gebruiken. Google adviseert echter om de grootte van de doorgegeven gegevens te minimaliseren — het is beter om een identificatie door te geven en het object op ID binnen het scherm te laden. Dit voorkomt problemen met grote geserialiseerde gegevens en vereenvoudigt de verwerking van configuratiewijzigingen.

kotlin
data class Profile(val id: Int, val name: String) : Parcelable

            // Navigeer met minimale gegevens
navController.navigate("profile/42")

            // Haal argumenten op het scherm op
composable(
    route = "profile/{userId}",
    arguments = listOf(
        NavArgument("userId") { type = NavType.IntType }
    )
) { backStackEntry ->
    val userId = backStackEntry.arguments?.getInt("userId") ?: 0
    ProfileDetailScreen(userId = userId)
}

Geneste navigatie met composable()

In echte applicaties is het vaak nodig om geneste navigatiegrafen te organiseren — bijvoorbeeld een aparte stapel schermen binnen een tabblad van BottomNavigation. composable() ondersteunt nesting via het mechanisme van geneste NavHosts: binnen een composable-scherm kan een eigen NavHost met een onafhankelijke routestapel worden gedeclareerd.

Elke geneste NavHost heeft zijn eigen NavController en back stack. Dit betekent dat navigatie binnen een tabblad geen invloed heeft op navigatie in andere tabbladen — de gebruiker kan vrij schakelen tussen tabbladen zonder de navigatiegeschiedenis binnen elk ervan te verliezen. Deze architectuur wordt Scoped Navigation genoemd en wordt door Google aanbevolen voor applicaties met complexe meerlaagse navigatie.

Bij het implementeren van geneste navigatie is het belangrijk om de status van NavController correct te beheren: elke geneste NavHost moet zijn eigen rememberNavController binnen de scope van de composable-functie bewaren. Volgens gegevens van Android Developer Summit 2024 gebruikt meer dan 40% van de Jetpack Compose-applicaties met drie of meer tabbladen de architectuur van geneste NavHosts voor isolatie van navigatie tussen modules.

kotlin
// Hoofd-NavHost met tabbladen
composable("tabs") {
    MainTabsScreen { tab ->
        when (tab) {
            Tab.Home -> HomeNavGraph()
            Tab.Search -> SearchNavGraph()
        }
    }
}

// Geneste graaf in Home-tabblad
@Composable
fun HomeNavGraph() {
    val navController = rememberNavController()
    NavHost(
        navController = navController,
        startDestination = "home_feed"
    ) {
        composable("home_feed") { FeedScreen() }
        composable("home_detail/{postId}") { PostDetailScreen() }
    }
}

Vergelijking van composable() met Intent-navigatie

Vóór Jetpack Compose gebruikte de standaard navigatiemethode in Android Intent en FragmentManager. Intent — is een systeembericht dat een nieuwe Activity start, wat het opnieuw aanmaken van de hele View-boom inhoudt. In tegenstelling hiermee werkt composable() binnen één Activity en vervangt het eenvoudig een deel van de Compose-boom, wat aanzienlijk sneller en geheugenefficiënter is.

Belangrijkste verschillen tussen composable() en Intent-gebaseerde navigatie:

  • Snelheid — composable() wisselt schermen in milliseconden zonder Activity opnieuw aan te maken, Intent vereist het opnieuw starten van de Activity.
  • Animaties — in Navigation Compose worden overgangsanimaties declaratief ingesteld via AnimatedNavHost, zonder dat overridePendingTransition nodig is.
  • Gedeelde status — composable() werkt in een gedeelde ViewModel-scope, wat het doorgeven van gegevens tussen schermen vereenvoudigt zonder Intent extras.
Kenmerkcomposable()Intent / Fragment
ArchitectuurSingle Activity, Compose-boomMulti Activity, Fragment-stapels
Gegevensoverdrachtpad/query-parameters, gedeelde ViewModelIntent extras, Bundle, SharedPreferences
Diepe linksIngebouwde ondersteuning voor navDeepLinkintent-filter in manifest
Back stackAutomatisch beheer van popBackStackFragmentManager.popBackStack()
Schakeltijd5–15 ms (binnen proces)50–200 ms (met herbouw)

De overgang van Intent naar composable() — is niet alleen een API-wijziging, maar een verandering van architectuurparadigma. In plaats van expliciet aan te geven welke Activity moet worden geopend, beschrijft de ontwikkelaar declaratief alle mogelijke routes op één plek, wat de leesbaarheid van de code verbetert en het testen van navigatie vereenvoudigt. Volgens gegevens van Google I/O 2024 vermindert Jetpack Compose met Navigation Compose de hoeveelheid navigatiecode met 40–60% in vergelijking met FragmentManager.

Veelvoorkomende fouten met composable()

Een van de meest voorkomende fouten — het opnieuw aanmaken van NavController bij recompositie. Als NavController wordt gemaakt via rememberNavController() op het niveau van de bovenliggende composable die kan worden herbouwd bij statuswijziging, raakt de navigatie beschadigd — de navigatiegeschiedenis gaat verloren. De juiste oplossing is om NavController naar een stabiel composable-niveau te tillen, bijvoorbeeld naar het niveau van Activity of de root-composable van de applicatie.

Het tweede veelvoorkomende probleem — oneindige recompositie tijdens navigatie. Dit gebeurt wanneer de aanroep navController.navigate() direct in het lichaam van de composable-functie is geplaatst. Omdat navigatie de status van NavHost verandert, veroorzaakt dit een recompositie die opnieuw navigate() aanroept, waardoor een cyclus ontstaat. Alle navigatie-aanroepen moeten worden ingepakt in lambda-handlers (onClick, onButtonPressed) en niet worden uitgevoerd in de compositie.

De derde fout — onjuist beheer van de back stack bij gebruik van BottomNavigation. Eenvoudige navigatie via navigate() bij elke tabbladwisseling voegt een nieuwe entry toe aan de stapel, in plaats van terug te keren naar de bestaande. Voor BottomNavigation moet navController.navigate() met restoreState = true en launchSingleTop = true worden gebruikt, wat zorgt voor correct herstel van de status bij het schakelen tussen tabbladen.

kotlin
fun NavController.navigateToTab(route: String) {
    navigate(route) {
        popUpTo(navController.graph.findStartDestination().id) {
            saveState = true
        }
        launchSingleTop = true
        restoreState = true
    }
}

Veelgestelde vragen

Wat is het verschil tussen composable() en een gewone @Composable-functie?

composable() — is geen annotatie, maar een extensiefunctie van NavHost die een route aan UI koppelt. Een gewone @Composable-functie beschrijft eenvoudig de layout, terwijl composable() deze layout registreert in de navigatiegraaf met de opgegeven route, waardoor deze toegankelijk wordt voor navigatie via NavController.

Hoe geef je een complex object door tussen composable()-schermen?

Het wordt aanbevolen om alleen een identificatie (ID) door te geven via een padparameter, en het object zelf op het scherm op ID te laden via een repository of ViewModel. Als het object toch moet worden doorgegeven, gebruik dan NavType.ParcelableType, maar vermijd het doorgeven van objecten groter dan 1 KB — dit kan leiden tot TransactionTooLargeException.

Waarom wordt een composable()-scherm opnieuw aangemaakt bij rotatie?

Rotatie van het scherm veroorzaakt een configuratiewijziging die standaard de Activity opnieuw aanmaakt. Om de status van composable-schermen te behouden, gebruik rememberSaveable voor eenvoudige gegevens of ViewModel met de scope van het betreffende scherm. Navigation Compose herstelt de back stack na herbouw, maar de status binnen composable()-functies wordt zonder rememberSaveable gereset.

Kan composable() worden gebruikt zonder NavHost?

Nee, composable() — is een extensiefunctie van NavGraphBuilder die alleen beschikbaar is binnen een NavHost-blok. Voor eenvoudige vervanging van een UI-gedeelte zonder navigatie, gebruik conditionele weergave (when, if) of AnimatedContent. composable() is specifiek bedoeld voor routering met ondersteuning voor back stack en diepe links.

Hoe onderscheid je de eerste overgang van terugkeer in composable()?

Gebruik SavedStateHandle binnen ViewModel: bij de eerste overgang geeft handle.get("initialized") null terug, bij terugkeer — de opgeslagen waarde. Als alternatief, analyseer de huidige positie in de back stack via navController.previousBackStackEntry — als deze null is, is dit het eerste scherm in de navigatiestapel.

Samenvatting

  • composable() — functie voor schermregistratie in NavHost, de belangrijkste manier om navigatie in Jetpack Compose te organiseren.
  • Routes — elk scherm wordt geïdentificeerd door een routetekst met optionele pad- en queryparameters.
  • Argumenten — worden doorgegeven via NavArgument met ondersteuning voor primitieve, Parcelable en Serializable types.
  • Nesting — composable() ondersteunt geneste NavHosts voor modulaire navigatie met onafhankelijke stapels.
  • Prestaties — lazy-initialisatie van schermen bespaart geheugen, schakeltijd tussen schermen is 5–15 ms.
  • Fouten — belangrijkste problemen: opnieuw aanmaken van NavController, oneindige recompositie bij aanroep van navigate() in composable-lichaam, onjuiste werking van BottomNavigation.
  • Migratie — overgang van FragmentManager naar composable() vermindert de hoeveelheid navigatiecode met 40–60% en elimineert een klasse fouten die verband houden met de Fragment-levenscyclus.

We ontwikkelen een mobiele applicatie turnkey

IT Sectr creëert sinds 2017 iOS- en Android-applicaties voor startups en bedrijven. We adviseren u en stellen de beste oplossing voor.

Bespreek het project

Lees ook