Single-Activity-Architektur
Die Single-Activity-Architektur strukturiert moderne Android-Apps um eine einzige Activity. Du lernst, wie Navigation und Lifecycle dabei zusammenspielen.
Moderne Android-Apps bestehen häufig aus Dutzenden von Screens, aber hinter all diesen Ansichten steckt – wenn du die Architektur richtig aufsetzt – oft nur eine einzige Activity. Die Single-Activity-Architektur ist heute die empfohlene Vorgehensweise für Jetpack-Compose-Apps und löst Probleme, mit denen Entwicklerinnen und Entwickler früherer Android-Generationen täglich zu kämpfen hatten: unübersichtliche Activity-zu-Activity-Übergänge, doppeltes Lifecycle-Management und schwer steuerbares Back-Stack-Verhalten.
Was ist das?
In traditionellen Android-Apps war es üblich, für jeden Hauptscreen eine eigene Activity zu erstellen. Jede Activity besaß ihren eigenen Lebenszyklus, ihren eigenen Back-Stack und ihr eigenes State-Management. Das führte zu redundantem Boilerplate-Code, schwer nachvollziehbaren Navigationsflüssen und fehleranfälligen Zustandsübergaben per Intent-Extras.
Die Single-Activity-Architektur dreht dieses Modell um: Statt vieler Activities gibt es genau eine Host-Activity, die als schlanker Container für alle Screens der App fungiert. Die einzelnen Screens werden als Jetpack-Compose-Composables innerhalb dieser Activity gerendert. Die Navigation zwischen Screens übernimmt die Navigation Component mit ihrem NavController und einem NavHost-Composable.
Im Kontext von Phase 6 – Modern Android Architecture – ist diese Entscheidung fundamental. Sie bildet das Fundament, auf dem ViewModels, State Hoisting und Dependency Injection sauber zusammenspielen können. Ohne eine klare Lifecycle-Grenze, die die Activity bietet, wäre es deutlich schwieriger, einen konsistenten App-State zu verwalten.
Wie funktioniert es?
Das Herzstück der Single-Activity-Architektur sind drei Bausteine, die eng zusammenarbeiten.
NavHost und NavController
Der NavHost ist ein Composable, das als Render-Container für alle navigierbaren Screens dient. Du definierst darin einen Navigationsgraphen – eine Menge von Routen mit zugehörigen Composables. Der NavController steuert, welche Route gerade angezeigt wird, und verwaltet den virtuellen Back-Stack automatisch. Du erstellst ihn mit rememberNavController() und übergibst ihn an den NavHost.
Back-Stack-Management
Weil alle Screens innerhalb einer einzigen Activity leben, verwaltet die Navigation Component einen eigenen virtuellen Back-Stack. Ein Tippen auf den Zurück-Button navigiert nicht zu einer anderen Activity, sondern poppt den obersten Eintrag aus diesem Stack. Das Verhalten ist dadurch vollständig kontrollierbar: Du entscheidest mit popUpTo, welche Screens beim Navigieren aus dem Stack entfernt werden, und mit launchSingleTop, ob eine Destination mehrfach auf dem Stack erlaubt ist.
Lifecycle-Bewusstsein
Da es nur eine Activity gibt, hängen alle Screens direkt an deren Lifecycle. ViewModels, die auf den ViewModelStoreOwner der Activity gescopted sind, leben so lange wie die Activity selbst – also über Screen-Wechsel hinaus. Das ist genau das, was du für geteilten State zwischen Screens brauchst, etwa für einen Warenkorb oder einen laufenden Suchfilter. Composables hingegen werden bei jeder Komposition neu berechnet; ihr lokaler State lebt nur so lange, wie sie in der Kompositionshierarchie verbleiben.
In der Praxis
Ein minimales Setup sieht so aus:
@Composable
fun AppNavGraph(navController: NavHostController) {
NavHost(
navController = navController,
startDestination = "home"
) {
composable("home") {
HomeScreen(
onItemClick = { id -> navController.navigate("detail/$id") }
)
}
composable("detail/{itemId}") { backStackEntry ->
val itemId = backStackEntry.arguments?.getString("itemId")
DetailScreen(itemId = itemId)
}
}
}
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val navController = rememberNavController()
AppNavGraph(navController = navController)
}
}
}
Die Activity selbst ist minimal: Sie setzt lediglich den NavHost als Root-Content. Alle Navigationslogik liegt im Composable-Graphen, und der NavController wird von der Activity nach unten weitergegeben.
Typische Stolperfalle: Daten als Argument vs. ViewModel
Ein häufiger Anfängerfehler ist es, größere Datenobjekte als Navigationsargumente mitzugeben – etwa ein komplettes Domain-Objekt als serialisierten JSON-String. Das ist fehleranfällig, aufwendig zu deserialisieren und umgeht das Architekturprinzip der Single Source of Truth. Die richtige Lösung: Übergib nur eine leichtgewichtige ID als Argument und lass den Ziel-Screen die vollständigen Daten selbst aus einem ViewModel oder Repository laden. Das entspricht der offiziellen Android-Architekturempfehlung, nach der jeder Screen seinen eigenen State selbst verantwortet.
Eine zweite Falle liegt im Back-Stack-Verhalten: Wenn du popUpTo und launchSingleTop nicht korrekt konfigurierst, können sich Screens mehrfach stapeln. Das führt zu unerwarteten Rücknavigationsschritten und potenziell doppeltem State. Prüfe das Verhalten mit dem Layout Inspector in Android Studio und teste explizit die Navigationssequenzen, die Nutzerinnen und Nutzer in deiner App am häufigsten durchlaufen.
Fazit
Die Single-Activity-Architektur ist kein Dogma, sondern ein pragmatisches Werkzeug: Für die überwiegende Mehrheit moderner Compose-Apps ist eine einzige Host-Activity die sauberste, wartbarste Lösung. Sie vereinfacht Lifecycle-Management, macht Übergänge konsistent und passt nahtlos zu Jetpack ViewModel, Navigation Component und Dependency Injection. Nur wenn technische Randbedingungen – etwa separate Prozesse, Multiwindow-Szenarien oder sehr unterschiedliche Task-Affinitäten – es erfordern, lohnt sich eine weitere Activity. Öffne jetzt den Navigationsgraphen deiner aktuellen App in Android Studio, zähle deine Activities und frage dich für jede einzelne: Brauche ich hier wirklich eine eigene Activity – oder reicht ein Composable im NavHost?