Die Migration von Views zu Compose ist zwar rein UI-bezogen, aber es gibt viele Dinge zu beachten, um eine sichere und inkrementelle Migration durchzuführen. Auf dieser Seite finden Sie einige Hinweise zur Migration Ihrer datenbankbasierten App zu Compose.
Design Ihrer App migrieren
Material Design ist das empfohlene Designsystem für die Gestaltung von Android-Apps.
Für ansichtsbasierte Apps sind drei Material-Versionen verfügbar:
- Material Design 1 mit der AppCompat-Bibliothek (z.B.
Theme.AppCompat.*
) - Material Design 2 mit der Bibliothek MDC-Android (d.h.
Theme.MaterialComponents.*
) - Material Design 3 mithilfe der Bibliothek MDC-Android (z.B.
Theme.Material3.*
)
Für Editoren-Apps sind zwei Versionen von Material verfügbar:
- Material Design 2 mit der Bibliothek Compose Material (
androidx.compose.material.MaterialTheme
) - Material Design 3 mit der Bibliothek Compose Material 3 (
androidx.compose.material3.MaterialTheme
)
Wir empfehlen die Verwendung der neuesten Version (Material 3), wenn das Designsystem Ihrer App dies zulässt. Es gibt Migrationsleitfäden sowohl für Views als auch für Compose:
- Material 1 zu Material 2 in Ansichten
- Material 2 bis Material 3 in Ansichten
- Material 2 bis Material 3 in Compose
Wenn Sie neue Bildschirme in Compose erstellen, müssen Sie unabhängig von der verwendeten Version von Material Design vor allen Compose-Elementen, die UI aus den Materialbibliotheken von Compose ausgeben, eine MaterialTheme
anwenden. Die Materialkomponenten (Button
, Text
usw.) hängen davon ab, dass eine MaterialTheme
vorhanden ist, und ihr Verhalten ist ohne dieses nicht definiert.
Alle Jetpack Compose-Beispiele verwenden ein benutzerdefiniertes Compose-Design, das auf MaterialTheme
basiert.
Weitere Informationen finden Sie unter Systeme in Compose und XML-Designs zu Compose migrieren.
Navigation
Wenn Sie die Navigationskomponente in Ihrer App verwenden, finden Sie weitere Informationen unter Navigation mit Compose – Interoperabilität und Jetpack Navigation zu Navigation Compose migrieren.
Benutzeroberfläche für die kombinierte Ansicht „Compose“ und „Views“ testen
Nachdem Sie Teile Ihrer App zu Compose migriert haben, ist es wichtig, sie zu testen, um sicherzustellen, dass keine Fehler aufgetreten sind.
Wenn eine Aktivität oder ein Fragment Compose verwendet, müssen Sie anstelle von ActivityScenarioRule
createAndroidComposeRule
verwenden. createAndroidComposeRule
integriert ActivityScenarioRule
in eine ComposeTestRule
, mit der Sie Code gleichzeitig schreiben und ansehen können.
class MyActivityTest { @Rule @JvmField val composeTestRule = createAndroidComposeRule<MyActivity>() @Test fun testGreeting() { val greeting = InstrumentationRegistry.getInstrumentation() .targetContext.resources.getString(R.string.greeting) composeTestRule.onNodeWithText(greeting).assertIsDisplayed() } }
Weitere Informationen finden Sie unter Layout von „Compose“ testen. Informationen zur Interoperabilität mit UI-Test-Frameworks finden Sie unter Interoperabilität mit Espresso und Interoperabilität mit UiAutomator.
Compose in Ihre bestehende App-Architektur einbinden
Architekturmuster für unidirektionalen Datenfluss (UDF) funktionieren nahtlos mit Compose. Wenn in der App stattdessen andere Arten von Architekturmustern wie Model View Presenter (MVP) verwendet werden, empfehlen wir, diesen Teil der Benutzeroberfläche vor oder während der Einführung von Compose zu UDF zu migrieren.
ViewModel
in der Eingabeleiste verwenden
Wenn Sie die Bibliothek der Architekturkomponenten ViewModel
verwenden, können Sie über jede zusammensetzbare Funktion auf eine ViewModel
zugreifen. Dazu rufen Sie die Funktion viewModel()
auf, wie unter Erstellung und andere Bibliotheken erläutert.
Wenn Sie Compose verwenden, sollten Sie denselben ViewModel
-Typ nicht in verschiedenen Composeables verwenden, da ViewModel
-Elemente dem Gültigkeitsbereich des Ansichtslebenszyklus folgen. Der Umfang ist entweder die Hostaktivität, das Fragment oder das Navigationsdiagramm, wenn die Navigationsbibliothek verwendet wird.
Wenn die Composeables beispielsweise in einer Aktivität gehostet werden, gibt viewModel()
immer dieselbe Instanz zurück, die erst gelöscht wird, wenn die Aktivität beendet ist.
Im folgenden Beispiel wird derselbe Nutzer („user1“) zweimal begrüßt, da dieselbe GreetingViewModel
-Instanz in allen Composeables unter der Hostaktivität wiederverwendet wird. Die erste erstellte ViewModel
-Instanz wird in anderen Composeables wiederverwendet.
class GreetingActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MaterialTheme { Column { GreetingScreen("user1") GreetingScreen("user2") } } } } } @Composable fun GreetingScreen( userId: String, viewModel: GreetingViewModel = viewModel( factory = GreetingViewModelFactory(userId) ) ) { val messageUser by viewModel.message.observeAsState("") Text(messageUser) } class GreetingViewModel(private val userId: String) : ViewModel() { private val _message = MutableLiveData("Hi $userId") val message: LiveData<String> = _message } class GreetingViewModelFactory(private val userId: String) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun <T : ViewModel> create(modelClass: Class<T>): T { return GreetingViewModel(userId) as T } }
Da Navigationsgraphen auch ViewModel
-Elemente umfassen, haben Composables, die ein Ziel in einem Navigationsgraphen sind, eine andere Instanz der ViewModel
.
In diesem Fall ist ViewModel
auf den Lebenszyklus des Ziels beschränkt und wird gelöscht, wenn das Ziel aus dem Backstack entfernt wird. Im folgenden Beispiel wird beim Aufrufen des Bildschirms Profil eine neue Instanz von GreetingViewModel
erstellt.
@Composable fun MyApp() { NavHost(rememberNavController(), startDestination = "profile/{userId}") { /* ... */ composable("profile/{userId}") { backStackEntry -> GreetingScreen(backStackEntry.arguments?.getString("userId") ?: "") } } }
„Source of Truth“
Wenn Sie Compose in einem Teil der Benutzeroberfläche verwenden, müssen Compose und der Systemcode der Ansicht möglicherweise Daten teilen. Wir empfehlen, diesen gemeinsamen Status nach Möglichkeit in einer anderen Klasse zu kapseln, die den von beiden Plattformen verwendeten Best Practices für UDFs entspricht, z. B. in einer ViewModel
, die einen Stream der freigegebenen Daten zur Ausgabe von Datenaktualisierungen freigibt.
Das ist jedoch nicht immer möglich, wenn die freigegebenen Daten veränderbar sind oder eng mit einem UI-Element verknüpft sind. In diesem Fall muss ein System als "Source of Truth" dienen und dieses System muss alle Datenaktualisierungen an das andere System weitergeben. Als Faustregel gilt: Die „Source of Truth“ sollte dem Element gehören, das der Wurzel der UI-Hierarchie am nächsten ist.
Als „Source of Truth“ zusammenstellen
Verwenden Sie das Attribut SideEffect
composable, um den Compose-Status in nicht Compose-Code zu veröffentlichen. In diesem Fall wird die maßgebliche Quelle in einem Composeable gespeichert, das Statusaktualisierungen sendet.
Ihre Analysebibliothek bietet Ihnen beispielsweise die Möglichkeit, die Nutzerpopulation zu segmentieren, indem Sie allen nachfolgenden Analyseereignissen benutzerdefinierte Metadaten (in diesem Beispiel Nutzereigenschaften) hinzufügen. Wenn Sie den Nutzertyp des aktuellen Nutzers an Ihre Analysebibliothek senden möchten, aktualisieren Sie den Wert mit SideEffect
.
@Composable fun rememberFirebaseAnalytics(user: User): FirebaseAnalytics { val analytics: FirebaseAnalytics = remember { FirebaseAnalytics() } // On every successful composition, update FirebaseAnalytics with // the userType from the current User, ensuring that future analytics // events have this metadata attached SideEffect { analytics.setUserProperty("userType", user.userType) } return analytics }
Weitere Informationen finden Sie unter Nebeneffekte in Compose.
System als „Source of Truth“ ansehen
Wenn das Ansichtssystem den Status besitzt und ihn für Compose freigibt, empfehlen wir, den Status in mutableStateOf
-Objekten zu verpacken, um ihn für Compose threadsicher zu machen. Bei diesem Ansatz werden zusammensetzbare Funktionen vereinfacht, da sie nicht mehr die Referenzquelle haben. Das Ansichtssystem muss jedoch den mutable-Status und die Ansichten aktualisieren, die diesen Status verwenden.
Im folgenden Beispiel enthält ein CustomViewGroup
einen TextView
und einen ComposeView
mit einer zusammensetzbaren TextField
-Funktion. Die TextView
muss den Inhalt anzeigen, den der Nutzer in die TextField
eingibt.
class CustomViewGroup @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyle: Int = 0 ) : LinearLayout(context, attrs, defStyle) { // Source of truth in the View system as mutableStateOf // to make it thread-safe for Compose private var text by mutableStateOf("") private val textView: TextView init { orientation = VERTICAL textView = TextView(context) val composeView = ComposeView(context).apply { setContent { MaterialTheme { TextField(value = text, onValueChange = { updateState(it) }) } } } addView(textView) addView(composeView) } // Update both the source of truth and the TextView private fun updateState(newValue: String) { text = newValue textView.text = newValue } }
Freigegebene UI migrieren
Wenn Sie schrittweise zu Compose migrieren, müssen Sie möglicherweise sowohl im Compose- als auch im View-System gemeinsame UI-Elemente verwenden. Wenn Ihre App beispielsweise eine benutzerdefinierte CallToActionButton
-Komponente hat, müssen Sie sie möglicherweise sowohl auf Compose- als auch auf datenbankbasierten Bildschirmen verwenden.
In Compose werden freigegebene UI-Elemente zu Composeables, die in der gesamten App wiederverwendet werden können, unabhängig davon, ob das Element mit XML gestylt wird oder eine benutzerdefinierte Ansicht ist. Sie können beispielsweise ein CallToActionButton
-Element für Ihre benutzerdefinierte Button
-Aufrufkomponente erstellen.
Wenn Sie das Composeable in ansichtsbasierten Bildschirmen verwenden möchten, erstellen Sie einen benutzerdefinierten Ansichts-Wrapper, der von AbstractComposeView
ausgeht. Platzieren Sie die von Ihnen erstellte zusammensetzbare Funktion in der überschriebenen zusammensetzbaren Funktion Content
, wie im folgenden Beispiel gezeigt, in Ihrem Entwurfsdesign:
@Composable fun CallToActionButton( text: String, onClick: () -> Unit, modifier: Modifier = Modifier, ) { Button( colors = ButtonDefaults.buttonColors( containerColor = MaterialTheme.colorScheme.secondary ), onClick = onClick, modifier = modifier, ) { Text(text) } } class CallToActionViewButton @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyle: Int = 0 ) : AbstractComposeView(context, attrs, defStyle) { var text by mutableStateOf("") var onClick by mutableStateOf({}) @Composable override fun Content() { YourAppTheme { CallToActionButton(text, onClick) } } }
Die zusammensetzbaren Parameter werden in der benutzerdefinierten Ansicht zu veränderbaren Variablen. Dadurch ist die benutzerdefinierte CallToActionViewButton
-Ansicht wie eine herkömmliche Ansicht skalierbar und nutzbar. Unten sehen Sie ein Beispiel für die Bindung an die Ansicht:
class ViewBindingActivity : ComponentActivity() { private lateinit var binding: ActivityExampleBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityExampleBinding.inflate(layoutInflater) setContentView(binding.root) binding.callToAction.apply { text = getString(R.string.greeting) onClick = { /* Do something */ } } } }
Wenn die benutzerdefinierte Komponente einen veränderbaren Status enthält, lesen Sie den Hilfeartikel Wahrheitsquelle für den Status.
Aufteilungsstatus gegenüber Präsentation priorisieren
Traditionell ist ein View
zustandsorientiert. Mit einem View
werden Felder verwaltet, die nicht nur beschreiben, was angezeigt werden soll, sondern auch wie es angezeigt werden soll. Wenn Sie eine View
in Compose konvertieren, sollten Sie die gerenderten Daten trennen, um einen unidirektionalen Datenfluss zu erreichen, wie im Abschnitt State Hoisting erläutert.
Ein View
hat beispielsweise eine visibility
-Eigenschaft, die beschreibt, ob er sichtbar, unsichtbar oder verschwunden ist. Das ist eine inhärente Eigenschaft der View
. Auch wenn andere Codeteile die Sichtbarkeit eines View
ändern können, ist nur das View
selbst wirklich darüber informiert, wie es aktuell sichtbar ist. Die Logik, die dafür sorgt, dass ein View
sichtbar ist, kann fehleranfällig sein und ist oft mit dem View
selbst verknüpft.
Mit „Compose“ lassen sich hingegen ganz einfach völlig unterschiedliche zusammensetzbare Funktionen mit bedingter Logik in Kotlin anzeigen:
@Composable fun MyComposable(showCautionIcon: Boolean) { if (showCautionIcon) { CautionIcon(/* ... */) } }
CautionIcon
muss nicht wissen, warum es angezeigt wird, und es gibt kein Konzept für visibility
: Es ist entweder in der Komposition oder nicht.
Wenn Sie die Zustandsverwaltung und die Darstellungslogik klar voneinander trennen, können Sie die Darstellung von Inhalten als Umwandlung des Zustands in die Benutzeroberfläche flexibler ändern. Die Möglichkeit, bei Bedarf zu winden, macht zusammensetzbare Funktionen auch besser wiederverwendbar, da die Statusinhaberschaft flexibler ist.
Für gekapselte und wiederverwendbare Komponenten werben
View
-Elemente wissen oft, wo sie sich befinden: in einer Activity
, einer Dialog
, einer Fragment
oder irgendwo in einer anderen View
-Hierarchie. Da sie oft aus statischen Layoutdateien erstellt werden, ist die Gesamtstruktur einer View
in der Regel sehr starr. Dies führt zu einer engeren Kopplung und erschwert die Änderung oder Wiederverwendung eines View
.
Ein benutzerdefiniertes View
kann beispielsweise davon ausgehen, dass es eine untergeordnete Ansicht eines bestimmten Typs mit einer bestimmten ID hat, und seine Eigenschaften direkt als Reaktion auf eine Aktion ändern. Dadurch sind diese View
-Elemente eng miteinander verbunden: Das benutzerdefinierte View
kann abstürzen oder beschädigt werden, wenn es das untergeordnete Element nicht findet, und das untergeordnete Element kann wahrscheinlich nicht ohne das übergeordnete benutzerdefinierte View
wiederverwendet werden.
Mit wiederverwendbaren Compose-Elementen ist das in Compose weniger ein Problem. Übergeordnete Elemente können Status und Rückrufe ganz einfach angeben. So können Sie wiederverwendbare Composeables schreiben, ohne genau zu wissen, wo sie verwendet werden.
@Composable fun AScreen() { var isEnabled by rememberSaveable { mutableStateOf(false) } Column { ImageWithEnabledOverlay(isEnabled) ControlPanelWithToggle( isEnabled = isEnabled, onEnabledChanged = { isEnabled = it } ) } }
Im obigen Beispiel sind alle drei Teile stärker gekapselt und weniger gekoppelt:
ImageWithEnabledOverlay
muss nur wissen, was der aktuelleisEnabled
-Status ist. Es muss nicht wissen, dassControlPanelWithToggle
existiert oder wie es gesteuert wird.ControlPanelWithToggle
weiß nicht, dassImageWithEnabledOverlay
existiert. Es kann null, eine oder mehrere Möglichkeiten für die Anzeige vonisEnabled
geben undControlPanelWithToggle
müsste sich nicht ändern.Für das übergeordnete Element spielt es keine Rolle, wie tief
ImageWithEnabledOverlay
oderControlPanelWithToggle
verschachtelt sind. Diese Kinder könnten Änderungen animieren, Inhalte austauschen oder Inhalte an andere Kinder weitergeben.
Dieses Muster wird als Inversion of Control bezeichnet. Weitere Informationen finden Sie in der CompositionLocal
-Dokumentation.
Änderungen der Bildschirmgröße verarbeiten
Verschiedene Ressourcen für unterschiedliche Fenstergrößen sind eine der wichtigsten Methoden zum Erstellen responsiver View
-Layouts. Qualifizierte Ressourcen sind zwar weiterhin eine Option für Layoutentscheidungen auf Bildschirmebene, mit Compose ist es jedoch viel einfacher, Layouts vollständig in Code mit normaler bedingter Logik zu ändern. Weitere Informationen finden Sie unter Fenstergrößenklassen verwenden.
Weitere Informationen zu den Techniken, die Compose für die Erstellung adaptiver Benutzeroberflächen bietet, finden Sie unter Unterstützung verschiedener Bildschirmgrößen.
Verschachteltes Scrollen mit Ansichten
Weitere Informationen zum Aktivieren der Interoperabilität für verschachtelte Scrollfunktionen zwischen scrollbaren Ansichtselementen und scrollbaren Composeables, die in beide Richtungen verschachtelt sind, finden Sie unter Interoperabilität für verschachtelte Scrollfunktionen.
In RecyclerView
schreiben
Composables in RecyclerView
sind seit RecyclerView
Version 1.3.0-alpha02 leistungsstark. Sie benötigen mindestens Version 1.3.0-alpha02 von RecyclerView
, um diese Vorteile nutzen zu können.
WindowInsets
Interoperabilität mit Ansichten
Möglicherweise müssen Sie die Standardeinfügungen überschreiben, wenn auf Ihrem Bildschirm sowohl Aufruf- als auch Editor-Code in derselben Hierarchie vorhanden sind. In diesem Fall müssen Sie angeben, welche der beiden Seiten die Einleger verwenden und welche sie ignorieren soll.
Wenn das äußerste Layout beispielsweise ein Android View-Layout ist, sollten Sie die Einfügungen im View-System übernehmen und beim Schreiben ignorieren.
Wenn Ihr äußerstes Layout ein Composeable ist, sollten Sie die Einzüge in Compose verwenden und die AndroidView
-Composeables entsprechend ausrichten.
Standardmäßig werden für jede ComposeView
alle Inset-Assets auf der Verbrauchsebene WindowInsetsCompat
verwendet. Wenn Sie dieses Standardverhalten ändern möchten, setzen Sie ComposeView.consumeWindowInsets
auf false
.
Weitere Informationen finden Sie in der Dokumentation WindowInsets
in Compose.
Empfehlungen für dich
- Hinweis: Der Linktext wird angezeigt, wenn JavaScript deaktiviert ist.
- Emojis anzeigen
- Material Design 2 in der compose-Ansicht
- Fenstereinzüge im Tool „Schreiben“