Composition ist der zentrale Prozess in Jetpack Compose, bei dem aus beschreibenden Composable-Funktionen ein lebendiger UI-Baum aufgebaut wird, der auf dem Bildschirm angezeigt wird. Anders als beim Android-View-System, wo Layouts aus XML geladen und in unveränderliche Objekte umgewandelt wurden, arbeitet Composition als dynamisches System: Funktionen werden ausgeführt, erzeugen Slots im Speicher, bilden eine Knotenhierarchie und binden sie an den Zustand. Laut Google Android Developers, 2026 ist das Verständnis von Composition entscheidend für die Optimierung der Leistung von Compose-Anwendungen.
Wichtigste Erkenntnisse
Composition ist der Prozess der Ausführung von Composable-Funktionen, der zu einer internen Darstellung der Benutzeroberfläche als Baum von Knoten führt. Jeder Knoten dieses Baums entspricht entweder einer integrierten Komponente (Text, Button, Image) oder einem Aufruf einer benutzerdefinierten Composable-Funktion. Composition erzeugt keine Android-View-Objekte direkt — es erstellt eine abstrakte Beschreibung, die dann von den Layout- und Drawing-Phasen verarbeitet wird.
Das Hauptmerkmal von Composition ist seine Neustartfähigkeit. Jede Composable-Funktion innerhalb der Komposition kann jederzeit neu gestartet werden, wenn sich ihre Eingabeparameter oder die von ihr gelesenen Zustandsobjekte geändert haben. Das System startet nicht den gesamten Baum neu — nur die Funktionen, die tatsächlich von den geänderten Daten abhängen.
Technisch wird Composition über Composer verwaltet — eine interne Engine, die der Kotlin-Compiler in jede Composable-Funktion einbettet. Composer schreibt in Slots (Positionsgruppen) Informationen darüber, welche Funktionen mit welchen Parametern und in welcher Reihenfolge aufgerufen wurden. Bei nachfolgenden Aufrufen vergleicht Composer die neuen Daten mit den gespeicherten Daten und entscheidet über einen Neustart.
Der Prozess des Aufbaus des UI-Baums beginnt mit dem Aufruf der Methode setContent innerhalb einer Activity oder eines Fragments. Diese Methode erstellt die anfängliche Composition und startet die Ausführung der root-Composable-Funktion. Dann fügt jede verschachtelte Composable-Funktion ihre Knoten zum Baum hinzu und bildet eine Hierarchie: Row enthält Text und Button, Column enthält Image und Card, und so weiter.
Jeder Baumknoten erhält einen eindeutigen Positionsschlüssel, basierend auf seiner Position im Quellcode. Dieser Schlüssel wird verwendet, um den Knoten bei nachfolgenden Ausführungen zu identifizieren. Der Positionsschlüssel ist der Grund, warum die Reihenfolge des Aufrufs von Composable-Funktionen nicht von Bedingungen abhängen sollte: Wenn in einem Durchlauf A -> B aufgerufen wird und im nächsten B -> A, kann Compose alte und neue Knoten nicht zuordnen.
@Composable
fun AppScreen() {
Column { // Column-Knoten (Position 1)
HeaderSection() // HeaderSection-Knoten (Position 2)
ContentSection() // ContentSection-Knoten (Position 3)
FooterSection() // FooterSection-Knoten (Position 4)
}
}
@Composable
fun HeaderSection() {
Row { // Row-Knoten (Position 2.1)
Text("Titel") // Text-Knoten (Position 2.2)
Icon(...) // Icon-Knoten (Position 2.3)
}
}
In diesem Beispiel erhält jeder Aufruf eine Position basierend auf der Reihenfolge im Code. Column (Position 1) enthält drei Kindknoten (Positionen 2, 3, 4). HeaderSection fügt zwei weitere Kindknoten hinzu (2.1, 2.2, 2.3). Wenn in der nächsten Recomposition ContentSection vor HeaderSection aufgerufen wird, kann Composer die Knoten nicht korrekt zuordnen — daher die Regel: Die Reihenfolge der Aufrufe von Composable-Funktionen muss stabil sein.
Der Zustand in Composition wird durch Objekte vom Typ State<T> verwaltet. Wenn eine Composable-Funktion einen Wert aus State über eine delegierte Eigenschaft (by) liest, registriert sie eine Abhängigkeit von diesem State. Wenn sich der Wert ändert, werden alle Funktionen, die diesen State gelesen haben, für einen Neustart in der nächsten Kompositionsphase markiert.
Der Mechanismus zur Abhängigkeitsregistrierung heißt Snapshot-System. Jedes Mal, wenn sich State ändert, zeichnet ein Snapshot alle Änderungen auf und benachrichtigt Composer, welche Funktionen von diesem State abhängen. Wichtig zu verstehen: Das Lesen von State in nicht-Composable-Code (z.B. in einem onClick-Lambda) registriert keine Abhängigkeit — nur das Lesen innerhalb einer Composable-Funktion oder in Lambdas, die im Kompositionskontext ausgeführt werden.
Das Snapshot-System arbeitet transaktional: Mehrere State-Änderungen innerhalb eines Ereignisses werden zu einer Transaktion zusammengefasst, was mehrere Recompositionen verhindert. Dies ist besonders wichtig bei der Verarbeitung von Gesten: Eine Bewegung ändert mehrere State-Objekte, aber Compose führt nur eine Recomposition durch.
@Composable
fun StateExample() {
var text by remember { mutableStateOf("Hello") }
var isVisible by remember { mutableStateOf(true) }
Column {
Text(text) // registriert Abhängigkeit von text
if (isVisible) { // registriert Abhängigkeit von isVisible
TextField(value = text, onValueChange = { text = it })
}
Button(onClick = { isVisible = !isVisible }) {
Text(if (isVisible) "Ausblenden" else "Anzeigen")
}
}
}
Das Ändern von text löst nur eine Recomposition von Column, Text und TextField aus. Column, Button und die isVisible-Bedingung bleiben unverändert. Diese Isolierung der Recomposition ist ein entscheidender Vorteil von Compose gegenüber Systemen, die den gesamten Bildschirm neu zeichnen. Jede Composable-Funktion verfolgt nur die State-Objekte, die sie direkt liest.
Composition und Recomposition sind zwei verschiedene Modi der Ausführung von Composable-Funktionen. Composition erfolgt einmalig bei der Erstellung des Bildschirms: Das System führt alle Composable-Funktionen mit Anfangswerten aus und erstellt den anfänglichen UI-Baum. Recomposition erfolgt mehrfach bei Datenänderungen: Das System startet nur die Funktionen neu, die vom geänderten Zustand abhängen.
Modus Composition aktiviert alle Baumknoten, weist jeder Funktion Slots zu und registriert alle Nachkommen. Recomposition arbeitet selektiv: Compose vergleicht neue und alte Parameterwerte jeder Funktion, und wenn sie sich nicht geändert haben — wird die Funktion nicht ausgeführt (Skipping).
Composition und Recomposition unterscheiden sich in den Kosten. Erste Composition ist teurer, da sie den vollständigen Baumaufbau und die Slot-Zuweisung erfordert. Recomposition ist günstiger, insbesondere wenn die meisten Funktionen stabil sind — ihre Parameter werden per equals verglichen, und Compose überspringt ihren Aufruf. Für maximale Leistung sollte man anstreben, dass die meisten Recompositionen möglichst wenige Funktionen betreffen.
| Merkmal | Composition | Recomposition |
|---|---|---|
| Wann es auftritt | Einmal, bei der ersten Anzeige | Mehrfach, bei Datenänderung |
| Umfang | Gesamter Baum | Nur geänderte Funktionen |
| Parametervergleich | Nicht durchgeführt | Für Skipping durchgeführt |
| Slot-Erstellung | Ja, alle Slots werden erstellt | Nur für neue Knoten |
CompositionLocal ist ein Mechanismus zur impliziten Datenübergabe durch den Kompositionsbaum. Er löst das Problem, wenn ein Parameter durch Dutzende verschachtelter Composable-Funktionen übergeben werden muss, die ihn nicht direkt verwenden. Statt einer expliziten Parameterkette werden Daten auf der obersten Ebene gesetzt und in jeder verschachtelten Funktion über CompositionLocal.current gelesen.
MaterialTheme ist das bekannteste Beispiel für CompositionLocal. Alle Compose-Komponenten lesen Farben, Typografie und Formen über MaterialTheme.colorScheme, MaterialTheme.typography, MaterialTheme.shapes, ohne sie über Parameter zu erhalten. Entwickler können ihre eigenen CompositionLocal für Daten wie den aktuellen Benutzer, Lokalisierungseinstellungen oder Bildschirmkonfiguration erstellen.
Eine wichtige Einschränkung: CompositionLocal sollte nicht für häufig wechselnde Daten verwendet werden (Scrollposition, Text in einem Eingabefeld). Eine Komponente, die CompositionLocal liest, wird bei jeder Wertänderung neu gestartet. Daher ist es für dynamische Daten besser, explizite Parameter oder State zu verwenden. CompositionLocal ist optimal für Konfigurationsdaten, die sich selten oder gar nicht ändern.
val LocalUser = compositionLocalOf<User?> { null }
@Composable
fun AppRoot(user: User, content: @Composable () -> Unit) {
CompositionLocalProvider(LocalUser.provides(user)) {
content()
}
}
@Composable
fun UserAvatar() {
val user = LocalUser.current // Lesen ohne expliziten Parameter
AsyncImage(model = user?.avatarUrl, contentDescription = "Avatar")
}
CompositionLocalProvider erstellt einen Gültigkeitsbereich, in dem LocalUser.current den angegebenen Wert zurückgibt. UserAvatar liest den Benutzer, ohne den Parameter explizit durch Zwischenfunktionen zu übergeben. Dies ist besonders wertvoll in tiefen Hierarchien, in denen Daten nur in wenigen Blattknoten benötigt werden.
Häufig gestellte Fragen
Das Ändern von State während der Composition plant eine neue Recomposition, die nach Abschluss der aktuellen ausgeführt wird. Es tritt keine Endlosschleife auf: Compose garantiert, dass jede Recomposition in einer separaten Transaktion des Snapshot-Systems durchgeführt wird.
Auf modernen Geräten dauert die Composition eines Bildschirms mit 50–100 Composable-Funktionen 1–5 ms. Google empfiehlt, innerhalb von 16 ms für einen 60fps-Frame zu bleiben. Wenn Composition dieses Limit überschreitet, verwenden Sie LazyColumn oder teilen Sie den Bildschirm in kleinere Funktionen auf.
Ein direkter manueller Start von Composition ist nicht möglich — er wird von Composer automatisch verwaltet. Sie können jedoch eine Recomposition erzwingen, indem Sie State ändern oder invalidate() auf dem root-composable aufrufen, wenn Sie Zugriff auf CompositionContext haben.
Die View-Hierarchie ist ein unveränderlicher Baum von Java-Objekten, der einmal erstellt wird. Composition ist ein virtueller Baum, der bei jeder Datenänderung neu aufgebaut wird. View speichert seinen Zustand in Instanzvariablen, Composition — in Slots, die an die Funktionsaufrufposition gebunden sind.
Wenn eine Composable-Funktion nicht mehr aufgerufen wird (z.B. eine if-Bedingung wird false), entfernt Composition ihren Knoten und löst die DisposableEffect-Bereinigung aus. Bei erneutem Erscheinen (if wird wieder true) wird ein neuer Knoten erstellt — der alte wird nicht wiederhergestellt.
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