composable() ist eine Funktion der Navigation Compose-Bibliothek, die einen Bildschirm im NavHost registriert und eine URL-Route mit dem Compose-Layout verbindet. Wenn die Navigation zu einer bestimmten Route wechselt, ruft Jetpack Compose die entsprechende composable-Funktion auf und zeigt sie als aktuellen Bildschirm an. Im Gegensatz zu FragmentManager oder Intent-basierter Navigation arbeitet composable() auf der Ebene einer einzigen Activity und wird vollständig über Kotlin DSL gesteuert. Laut Android Developers (2025) verwenden mehr als 73% der modernen Android-Anwendungen, die mit Jetpack Compose erstellt wurden, Navigation Compose für Bildschirmübergänge.
Wichtige Punkte
composable() ist eine Erweiterungsfunktion des NavHost-Objekts. Kotlin DSL ermöglicht es, sie innerhalb des NavHost-Blocks aufzurufen, um alle Bildschirme der Anwendung deklarativ zu beschreiben. Jeder Aufruf erstellt einen Eintrag im Navigationsgraphen, der eine Zeichenfolgenroute mit einer composable-Funktion verknüpft. Wenn ein Benutzer zu einer bestimmten Route navigiert, zeigt NavHost das entsprechende composable als aktuellen Bildschirm an und blendet den vorherigen aus.
Die Navigation Compose-Bibliothek wurde von Google im Jahr 2021 als Alternative zur Fragment-basierten Navigation für Jetpack Compose eingeführt. Der Hauptvorteil ist die vollständige Kompatibilität mit dem Compose-Paradigma: composable() arbeitet im selben Lebenszyklus wie andere Compose-Komponenten, ohne dass FragmentManager oder Transaktionen erforderlich sind. Dies eliminiert eine Klasse von Fehlern, die mit der Nichtübereinstimmung der Lebenszyklen von Fragment und Compose zusammenhängen.
Jedes composable() nimmt eine Zeichenfolgenroute und eine Lambda-Funktion entgegen, die ein NavBackStackEntry-Objekt erhält und Composable UI zurückgibt. Innerhalb des Lambdas kann über navController aus dem Gültigkeitsbereich auf NavController zugegriffen werden, was die Navigation zu anderen Bildschirmen ermöglicht. Diese Architektur macht die Navigation explizit und vorhersagbar.
@Composable
fun AppNavigation() {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = "home"
) {
composable("home") {
HomeScreen(
onNavigateToProfile = {
navController.navigate("profile")
}
)
}
composable("profile") {
ProfileScreen(
onBack = { navController.popBackStack() }
)
}
}
}
Jeder Aufruf von composable() erstellt einen Knoten mit einer eindeutigen Routenkennung im internen Graphen des NavHost. Wenn NavController navigate() ausführt, vergleicht die Bibliothek die angeforderte Route mit allen registrierten composable-Knoten und findet eine Übereinstimmung. Nach der Übereinstimmung wird ein NavBackStackEntry erstellt, auf den Navigationsstapel gelegt und die UI-Komposition gestartet.
Die interne Implementierung von composable() verwendet einen Lazy-Initialisierungs-Mechanismus: Die Bildschirmkomposition erfolgt nur bei der ersten Navigation zu dieser Route. Das bedeutet, dass Bildschirme, zu denen der Benutzer noch nie navigiert ist, keinen Speicher belegen und keinen Code ausführen. Dieser Ansatz verbessert die Leistung in Anwendungen mit vielen Bildschirmen erheblich.
Der key-Parameter in composable() ermöglicht die Verwaltung der Bildschirmneuerstellung. Standardmäßig wird composable bei wiederholter Navigation zur selben Route nicht neu erstellt — NavHost verwendet den vorhandenen Back-Stack-Eintrag. Wenn jedoch ein key übergeben wird und sich dieser ändert, erstellt NavHost eine neue Instanz der composable-Funktion. Dies ist nützlich für Bildschirme mit dynamischen Daten, bei denen beim erneuten Öffnen ein Zustands-Update erzwungen werden muss.
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)
}
composable() unterstützt ein flexibles Argumentsystem über den Parameter arguments. Jedes Argument wird durch ein NavArgument-Objekt beschrieben, das den Typ, den Standardwert und die Erforderlichkeit definiert. Argumente werden in der Route als Pfadparameter (über geschweifte Klammern) oder als Abfrageparameter (über ein Fragezeichen) übergeben.
Pfadparameter werden direkt in der Routenvorlage angegeben: "profile/{userId}". Bei der Navigation zu "profile/42" extrahiert NavHost automatisch den Wert 42 und macht ihn über backStackEntry.arguments zugänglich. Abfrageparameter werden nach dem Fragezeichen hinzugefügt: "search?query={text}" und werden ebenfalls automatisch von der Bibliothek geparst.
Beim Extrahieren von Argumenten ist es wichtig, die Erforderlichkeit des Parameters über NavType.isNullableAllowed zu prüfen und Standardwerte über NavArgument defaultValue bereitzustellen. Wenn ein erforderlicher Parameter fehlt, löst Navigation Compose eine IllegalArgumentException aus, die subtile Fehler mit falschen Routen verhindert.
| Argumenttyp | NavType | Beispiel in Route |
|---|---|---|
| Int | NavType.IntType | "item/{id}" |
| String | NavType.StringType | "user/{name}" |
| Boolean | NavType.BoolType | "filter?enabled={value}" |
| Float | NavType.FloatType | "map/{lat}/{lon}" |
| Long | NavType.LongType | "article/{timestamp}" |
Für die Übergabe komplexer Objekte wird die Verwendung von NavType.ParcelableType oder NavType.SerializableType empfohlen. Google rät jedoch, die Größe der übertragenen Daten zu minimieren — es ist besser, eine Kennung zu übergeben und das Objekt innerhalb des Bildschirms anhand der ID zu laden. Dies verhindert Probleme mit großen serialisierten Daten und vereinfacht die Behandlung von Konfigurationsänderungen.
data class Profile(val id: Int, val name: String) : Parcelable
// Mit minimalen Daten navigieren
navController.navigate("profile/42")
// Argumente auf dem Bildschirm abrufen
composable(
route = "profile/{userId}",
arguments = listOf(
NavArgument("userId") { type = NavType.IntType }
)
) { backStackEntry ->
val userId = backStackEntry.arguments?.getInt("userId") ?: 0
ProfileDetailScreen(userId = userId)
}
In realen Anwendungen ist es oft notwendig, verschachtelte Navigationsgraphen zu organisieren — zum Beispiel einen separaten Bildschirmstapel innerhalb eines BottomNavigation-Tabs. composable() unterstützt die Verschachtelung durch verschachtelte NavHosts: Innerhalb eines composable-Bildschirms können Sie Ihren eigenen NavHost mit einem unabhängigen Routenstapel deklarieren.
Jeder verschachtelte NavHost hat seinen eigenen NavController und Back-Stack. Dies bedeutet, dass die Navigation innerhalb eines Tabs die Navigation in anderen Tabs nicht beeinflusst — der Benutzer kann frei zwischen Tabs wechseln, ohne die Navigationshistorie innerhalb eines jeden Tabs zu verlieren. Diese Architektur wird Scoped Navigation genannt und von Google für Anwendungen mit komplexer mehrstufiger Navigation empfohlen.
Bei der Implementierung verschachtelter Navigation ist es wichtig, den NavController-Zustand korrekt zu verwalten: Jeder verschachtelte NavHost sollte seinen eigenen rememberNavController innerhalb des Gültigkeitsbereichs der composable-Funktion speichern. Laut Android Developer Summit 2024 verwenden mehr als 40% der Jetpack Compose-Anwendungen mit drei oder mehr Tabs die Architektur verschachtelter NavHosts, um die Navigation zwischen Modulen zu isolieren.
// Haupt-NavHost mit Tabs
composable("tabs") {
MainTabsScreen { tab ->
when (tab) {
Tab.Home -> HomeNavGraph()
Tab.Search -> SearchNavGraph()
}
}
}
// Verschachtelter Graph im Home-Tab
@Composable
fun HomeNavGraph() {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = "home_feed"
) {
composable("home_feed") { FeedScreen() }
composable("home_detail/{postId}") { PostDetailScreen() }
}
}
Vor Jetpack Compose verwendete die Standardmethode der Navigation in Android Intent und FragmentManager. Intent ist eine Systemnachricht, die eine neue Activity startet, was die Neuerstellung des gesamten View-Baums bedeutet. Im Gegensatz dazu arbeitet composable() innerhalb einer einzigen Activity und ersetzt einfach einen Teil des Compose-Baums, was wesentlich schneller und speichereffizienter ist.
Hauptunterschiede zwischen composable() und Intent-basierter Navigation:
| Eigenschaft | composable() | Intent / Fragment |
|---|---|---|
| Architektur | Single Activity, Compose-Baum | Multi Activity, Fragment-Stapel |
| Datenübertragung | Pfad-/Abfrageparameter, gemeinsames ViewModel | Intent extras, Bundle, SharedPreferences |
| Deep Links | Integrierte navDeepLink-Unterstützung | intent-filter im Manifest |
| Back-Stack | Automatische popBackStack-Verwaltung | FragmentManager.popBackStack() |
| Wechselzeit | 5–15 ms (prozessintern) | 50–200 ms (mit Neuerstellung) |
Der Wechsel von Intent zu composable() ist nicht nur ein API-Austausch, sondern ein Wandel des Architekturparadigmas. Anstatt explizit anzugeben, welche Activity geöffnet werden soll, beschreibt der Entwickler alle möglichen Routen deklarativ an einem Ort, was die Lesbarkeit des Codes verbessert und das Testen der Navigation vereinfacht. Laut Google I/O 2024 reduziert Jetpack Compose mit Navigation Compose den Navigationscode um 40–60% im Vergleich zu FragmentManager.
Einer der häufigsten Fehler ist die Neuerstellung des NavController während der Rekomposition. Wenn NavController über rememberNavController() auf der Ebene des übergeordneten composable erstellt wird, das bei Zustandsänderungen neu erstellt werden kann, bricht die Navigation zusammen — die Historie geht verloren. Die richtige Lösung ist, NavController auf eine stabile composable-Ebene zu heben, wie die Activity-Ebene oder das Root-composable der Anwendung.
Das zweite häufige Problem ist die endlose Rekomposition während der Navigation. Dies tritt auf, wenn navController.navigate() direkt im Rumpf einer composable-Funktion platziert wird. Da die Navigation den Zustand des NavHost ändert, löst dies eine Rekomposition aus, die erneut navigate() aufruft, was eine Schleife erzeugt. Alle Navigationsaufrufe sollten in Lambda-Handler (onClick, onButtonPressed) eingebettet und nicht in der Komposition ausgeführt werden.
Der dritte Fehler ist die falsche Back-Stack-Behandlung bei Verwendung von BottomNavigation. Die einfache Navigation über navigate() bei jedem Tab-Wechsel fügt dem Stapel einen neuen Eintrag hinzu, anstatt zum vorhandenen zurückzukehren. Für BottomNavigation sollten Sie navController.navigate() mit restoreState = true und launchSingleTop = true verwenden, was die korrekte Zustandswiederherstellung beim Tab-Wechsel gewährleistet.
fun NavController.navigateToTab(route: String) {
navigate(route) {
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
}
Häufig gestellte Fragen
composable() ist keine Annotation, sondern eine Erweiterungsfunktion von NavHost, die eine Route an die UI bindet. Eine normale @Composable-Funktion beschreibt lediglich das Layout, während composable() dieses Layout mit einer bestimmten Route im Navigationsgraphen registriert und es so für die Navigation über NavController zugänglich macht.
Es wird empfohlen, nur eine Kennung (ID) über den Pfadparameter zu übergeben und das Objekt auf dem Bildschirm anhand der ID über ein Repository oder ViewModel zu laden. Wenn Sie das Objekt dennoch übergeben müssen, verwenden Sie NavType.ParcelableType, vermeiden Sie jedoch die Übergabe von Objekten größer als 1 KB — dies kann zu einer TransactionTooLargeException führen.
Die Bildschirmdrehung löst eine Konfigurationsänderung aus, die standardmäßig die Activity neu erstellt. Um den Zustand von composable-Bildschirmen zu bewahren, verwenden Sie rememberSaveable für einfache Daten oder ViewModel mit dem Gültigkeitsbereich dieses Bildschirms. Navigation Compose stellt den Back-Stack nach der Neuerstellung wieder her, aber der Zustand innerhalb von composable()-Funktionen wird ohne rememberSaveable zurückgesetzt.
Nein, composable() ist eine Erweiterungsfunktion von NavGraphBuilder, die nur innerhalb des NavHost-Blocks verfügbar ist. Für einfache UI-Ersetzung ohne Navigation verwenden Sie bedingtes Rendering (when, if) oder AnimatedContent. composable() ist speziell für das Routing mit Back-Stack- und Deep-Link-Unterstützung konzipiert.
Verwenden Sie SavedStateHandle innerhalb des ViewModel: Bei der ersten Navigation gibt handle.get("initialized") null zurück; bei der Rückwärtsnavigation den gespeicherten Wert. Alternativ analysieren Sie die aktuelle Position im Back-Stack über navController.previousBackStackEntry — wenn sie null ist, ist dies der erste Bildschirm im Navigationsstapel.
Zusammenfassung
Wir entwickeln eine mobile Applikation schlüsselfertig
IT Sectr entwickelt seit 2017 iOS- und Android-Apps für Startups und Unternehmen. Wir beraten Sie und schlagen die beste Lösung vor.
Lesen Sie auch