Android Coden
Android 5 min lesen

Compose Navigation Basics: Routing im modernen Android

Lerne die Grundlagen der Navigation in Jetpack Compose. Erfahre, wie du NavHost, Destinations und den Back Stack für flüssige App-Abläufe nutzt.

Die Navigation zwischen verschiedenen Bildschirmen ist das funktionale Rückgrat jeder Android-App. In der Welt von Jetpack Compose verabschieden wir uns von den traditionellen Fragment-Transaktionen und komplexen Intents, um stattdessen einen vollständig deklarativen Ansatz für das Routing zu nutzen. Mit den Compose Navigation Basics strukturierst du deine App als einen Graphen aus zustandsgesteuerten Composable-Funktionen, die nahtlos ineinandergreifen. Dieser Artikel zeigt dir die grundlegenden Mechanismen, damit du eine saubere Architektur für den Wechsel zwischen deinen App-Bildschirmen aufbauen kannst.

Was ist das?

Compose Navigation ist die offizielle Bibliothek von Google, um innerhalb einer Single-Activity-Architektur zwischen verschiedenen UI-Komponenten zu wechseln. Anstatt schwerfällige Activities oder Fragments übereinander zu stapeln, tauschst du auf dem Bildschirm lediglich Composables aus. Die Architektur stützt sich auf drei zentrale Säulen: den NavController, den NavHost und die Destinations (Zielorte).

Der NavController ist die zentrale Steuereinheit, die den Wechsel zwischen den Bildschirmen koordiniert und den Zustand der Navigation verwaltet. Der NavHost ist der sichtbare Container in deiner Benutzeroberfläche, der das Framework mit deinem Navigationsgraphen verbindet. Er entscheidet basierend auf der aktuellen Route, welches Composable gerendert wird. Eine Destination ist eine eindeutig identifizierbare Route innerhalb dieses Graphen, die an ein konkretes Composable gebunden ist. Zusammen definieren sie, wie Nutzer durch deine App steuern können.

Ein weiteres essenzielles Konzept ist der Back Stack (Rückstapel). Der NavController pflegt diesen Stack automatisch im Hintergrund. Er merkt sich die chronologische Reihenfolge der besuchten Routen. Wenn ein Nutzer die systemeigene Zurück-Taste des Android-Geräts betätigt oder eine Wischgeste vom Bildschirmrand ausführt, navigiert das System durch diesen Stack zurück und bringt den Nutzer zum vorherigen Bildschirm. Dieser deklarative Ansatz reduziert den sonst üblichen Boilerplate-Code enorm und macht den Datenfluss zwischen den Screens deutlich vorhersehbarer.

Wie funktioniert es?

Die Implementierung der Compose Navigation beginnt immer mit der Initialisierung eines NavControllers. Diesen erzeugst du typischerweise auf der obersten Ebene deiner App-Hierarchie, oft direkt in der MainActivity, mit der Funktion rememberNavController(). Durch diese hohe Platzierung im UI-Baum stellst du sicher, dass der Navigationszustand Recompositions unbeschadet übersteht und an alle relevanten Composables weitergegeben werden kann.

Sobald der NavController existiert, definierst du den NavHost. Der NavHost benötigt zwingend zwei Parameter: die Instanz des NavControllers und eine Startroute. Innerhalb des NavHost-Blocks baust du deinen Navigationsgraphen auf. Dafür nutzt du die Funktion composable(), um Routen zu registrieren. In der modernen Android-Entwicklung verwendest du für Routen bevorzugt typsichere Objekte – meistens Kotlin Data Classes oder Objects, die mit der @Serializable-Annotation von Kotlin Serialization versehen sind. Dies garantiert, dass Parameter fehlerfrei und typsicher von einem Bildschirm zum nächsten übergeben werden.

Wenn ein Nutzer von Bildschirm A zu Bildschirm B wechseln soll, ruft die Logik (beispielsweise ein Button-Click-Listener) die Methode navController.navigate(RouteB) auf. Der NavController aktualisiert daraufhin seinen internen Zustand, legt Route A auf den Back Stack und instruiert den NavHost, das mit Route B verknüpfte Composable zu rendern. Betätigt der Nutzer die Zurück-Taste oder rufst du programmatisch navController.popBackStack() auf, wird Route B vom Stack entfernt. Das bedeutet, das Composable wird zerstört und Route A wird wiederhergestellt. Dieser Lebenszyklus ist strikt an den Back Stack gebunden. Sobald eine Destination den Stack verlässt, wird auch ihr zugehöriger lokaler State sowie das verknüpfte ViewModel (sofern es an diese Route gebunden war) bereinigt. Dies verhindert Speicherlecks und stellt sicher, dass Nutzer bei einem erneuten Besuch einen völlig frischen Zustand vorfinden.

In der Praxis

In der täglichen Entwicklung wirst du schnell feststellen, dass das Durchreichen des NavControllers an jedes einzelne Composable unübersichtlich wird. Es koppelt deine UI-Komponenten hart an die Navigationsbibliothek und erschwert isolierte UI-Tests. Eine bewährte Architektur-Regel lautet daher: Übergebe niemals den NavController selbst an untergeordnete UI-Screens. Reiche stattdessen einfache Lambda-Funktionen (Callbacks) weiter, die das gewünschte Event an den übergeordneten Navigationsgraphen delegieren.

Hier ist ein praxisnahes Beispiel für eine moderne, typsichere Navigation:

import androidx.compose.runtime.Composable
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.toRoute
import kotlinx.serialization.Serializable

// 1. Definition der typsicheren Routen
@Serializable
object HomeRoute

@Serializable
data class ProfileRoute(val userId: String)

@Composable
fun AppNavigation() {
    val navController = rememberNavController()

    NavHost(
        navController = navController,
        startDestination = HomeRoute
    ) {
        composable<HomeRoute> {
            // Übergabe von Lambdas statt des NavControllers
            HomeScreen(
                onNavigateToProfile = { id ->
                    navController.navigate(ProfileRoute(userId = id))
                }
            )
        }

        composable<ProfileRoute> { backStackEntry ->
            // Typsicheres Auslesen der Argumente
            val profileRoute: ProfileRoute = backStackEntry.toRoute()
            ProfileScreen(
                userId = profileRoute.userId,
                onBackClick = { navController.popBackStack() }
            )
        }
    }
}

Eine typische Stolperfalle bei der Arbeit mit dem NavHost ist das unbeabsichtigte Aufblähen des Back Stacks. Wenn Nutzer beispielsweise über eine Bottom Navigation Bar wiederholt zwischen einem “Home”- und einem “Dashboard”-Tab wechseln, würdest du beim Standardverhalten bei jedem Klick eine neue Instanz der Route auf den Stack legen. Die Zurück-Taste würde den Nutzer dann zwingen, dutzende Male durch exakt dieselben Ansichten zurückzukehren, bevor die App geschlossen wird.

Nutze daher die Navigations-Optionen, um den Stack sauber zu halten. Wenn du navigierst, kannst du das Verhalten konfigurieren:

navController.navigate(HomeRoute) {
    popUpTo(navController.graph.startDestinationId) {
        saveState = true
    }
    launchSingleTop = true
    restoreState = true
}

Mit launchSingleTop = true verhinderst du, dass dieselbe Destination mehrfach hintereinander auf den Stack gelegt wird. Die Kombination aus popUpTo und restoreState sorgt dafür, dass die Navigation zwischen Haupt-Tabs den Zustand speichert und wiederherstellt, ohne einen endlosen Rückstapel aufzubauen.

Fazit

Um das Routing in Compose wirklich zu beherrschen, solltest du den Navigationsgraphen in einem kleinen Testprojekt mit mindestens drei verknüpften Bildschirmen implementieren. Nutze den Android Studio Debugger oder Log-Ausgaben, um den Status des NavControllers zu inspizieren, während du zwischen den Ansichten wechselst. Überprüfe explizit, wie sich die Anzahl der Einträge im Back Stack verändert, wenn du Parameter übergibst oder die Zurück-Taste betätigst. Teste verschiedene Navigationseinstellungen wie launchSingleTop. Wenn du diese Mechanik zur Stack-Verwaltung und typsicheren Parameterübergabe aktiv im Code erprobst, erlangst du die nötige Sicherheit für robuste, skalierbare App-Architekturen.

Quellen (1)
Redaktion

Geschrieben von

Redaktion

Das Redaktionsteam recherchiert und schreibt Artikel zu aktuellen Themen rund um Tech, Lifestyle und Ratgeber.