Auch wenn die Migration von Views zu Compose nur die UI betrifft, müssen bei einer sicheren und inkrementellen Migration viele Aspekte berücksichtigt werden. Auf dieser Seite finden Sie einige Überlegungen zur Migration Ihrer View-basierten Anwendung zu Compose.
Anwendungsdesign migrieren
Material Design ist das empfohlene Designsystem für die Designentwicklung von Android-Apps.
Für ansichtsbasierte Apps sind drei Versionen von Material verfügbar:
- Material Design 1 mithilfe der AppCompat-Bibliothek (z.B.
Theme.AppCompat.*
) - Material Design 2 mit der MDC-Android-Bibliothek (z.B.
Theme.MaterialComponents.*
) - Material Design 3 mit der MDC-Android-Bibliothek (z.B.
Theme.Material3.*
)
Für Compose-Apps sind zwei Versionen von Material verfügbar:
- Material Design 2 mithilfe der Compose Material-Bibliothek (d.h.
androidx.compose.material.MaterialTheme
) - Material Design 3 mithilfe der Compose Material 3-Bibliothek (z.B.
androidx.compose.material3.MaterialTheme
)
Wir empfehlen, die neueste Version (Material 3) zu verwenden, wenn das Designsystem Ihrer App dies in der Lage ist. Für Ansichten und für die Funktion „Compose“ sind Migrationsleitfäden verfügbar:
- Material 1 zu Material 2 in Views
- Material 2 zu Material 3 in Views
- Material 2 bis Material 3 in Compose
Wenn Sie neue Bildschirme in Compose erstellen, achten Sie unabhängig von der verwendeten Material Design-Version darauf, dass Sie vor allen zusammensetzbaren Funktionen, die eine UI aus den Compose-Material-Bibliotheken ausgeben, ein MaterialTheme
-Objekt anwenden. Die Materialkomponenten (Button
, Text
usw.) hängen davon ab, ob ein MaterialTheme
vorhanden ist, und ihr Verhalten ist ohne dieses nicht definiert.
In allen Jetpack Compose-Beispielen wird ein benutzerdefiniertes Compose-Design verwendet, das auf MaterialTheme
basiert.
Weitere Informationen finden Sie unter Designsysteme in Compose und XML-Designs zu Compose migrieren.
Navigation
Wenn Sie die Navigationskomponente in Ihrer Anwendung verwenden, finden Sie weitere Informationen unter Navigation mit Compose – Interoperability und Migration von Jetpack-Navigation zu Navigation Compose.
UI für gemischte Erstellungen/Ansichten testen
Nachdem Sie Teile Ihrer Anwendung zu Compose migriert haben, sollten Sie unbedingt Tests durchführen, um sicherzustellen, dass nichts fehlerhaft ist.
Wenn für eine Aktivität oder ein Fragment die Funktion „Compose“ verwendet wird, müssen Sie createAndroidComposeRule
anstelle von ActivityScenarioRule
verwenden. createAndroidComposeRule
bindet ActivityScenarioRule
in eine ComposeTestRule
ein, mit der Sie Code zum Schreiben und Ansehen gleichzeitig testen 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 testen. Informationen zur Interoperabilität mit UI-Test-Frameworks finden Sie unter Interoperabilität mit Espresso und Interoperabilität mit UiAutomator.
Compose in eine vorhandene Anwendungsarchitektur einbinden
Architekturmuster für den unidirektionalen Datenfluss (UDF) funktionieren nahtlos mit Compose. Wenn die Anwendung stattdessen andere Typen von Architekturmustern wie Model View Presenter (MVP) verwendet, empfehlen wir, diesen Teil der UI vor oder während der Einführung von Compose zu UDF zu migrieren.
ViewModel
in „Compose“ verwenden
Wenn Sie die ViewModel
-Bibliothek für Architekturkomponenten verwenden, können Sie von jeder zusammensetzbaren Funktion auf ein ViewModel
zugreifen. Dazu rufen Sie die Funktion viewModel()
auf, wie unter Compose und andere Bibliotheken beschrieben.
Achten Sie beim Verwenden von „Compose“ darauf, denselben ViewModel
-Typ in verschiedenen zusammensetzbaren Funktionen zu verwenden, da ViewModel
-Elemente den Bereichen des View-Lebenszyklus folgen. Der Bereich ist entweder die Hostaktivität, das Fragment oder das Navigationsdiagramm, wenn die Navigationsbibliothek verwendet wird.
Wenn die zusammensetzbaren Funktionen beispielsweise in einer Aktivität gehostet werden, gibt viewModel()
immer dieselbe Instanz zurück, die erst gelöscht wird, wenn die Aktivität abgeschlossen ist.
Im folgenden Beispiel wird derselbe Nutzer („user1“) zweimal begrüßt, da dieselbe GreetingViewModel
-Instanz in allen zusammensetzbaren Funktionen unter der Hostaktivität wiederverwendet wird. Die erste erstellte ViewModel
-Instanz wird in anderen zusammensetzbaren Funktionen 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 Navigationsdiagramme auch ViewModel
-Elemente umfassen, haben zusammensetzbare Funktionen, die ein Ziel in einem Navigationsdiagramm sind, eine andere Instanz von 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. Wenn der Nutzer im folgenden Beispiel den Bildschirm Profil aufruft, wird eine neue Instanz von GreetingViewModel
erstellt.
@Composable fun MyApp() { NavHost(rememberNavController(), startDestination = "profile/{userId}") { /* ... */ composable("profile/{userId}") { backStackEntry -> GreetingScreen(backStackEntry.arguments?.getString("userId") ?: "") } } }
Staatliche Informationsquelle
Wenn Sie Compose in einem Teil der Benutzeroberfläche verwenden, ist es möglich, dass Compose und der Systemcode „View“ Daten freigeben müssen. Kapseln Sie diesen gemeinsamen Status nach Möglichkeit in einer anderen Klasse, die den Best Practices für UDFs entspricht, die von beiden Plattformen verwendet werden. Verwenden Sie beispielsweise eine ViewModel
, die einen Stream der freigegebenen Daten zur Ausgabe von Datenaktualisierungen zur Verfügung stellt.
Dies ist jedoch nicht immer möglich, wenn die freizugebenden Daten änderbar oder eng an ein UI-Element gebunden sind. In diesem Fall muss ein System die „Source of Truth“ sein und dieses System muss alle Datenaktualisierungen an das andere System weitergeben. Als Faustregel gilt: Als Datenquelle sollte das Element dienen, das sich näher am Stamm der UI-Hierarchie befindet.
Das Verfassen als Quelle der Wahrheit
Verwenden Sie die zusammensetzbare Funktion SideEffect
, um den Erstellungsstatus in Nicht-Compose-Code zu veröffentlichen. In diesem Fall wird die Datenquelle in einer zusammensetzbaren Funktion gespeichert, die Statusaktualisierungen sendet.
In der Analysebibliothek können Sie beispielsweise die Nutzerpopulation segmentieren, indem Sie allen nachfolgenden Analyseereignissen benutzerdefinierte Metadaten (in diesem Beispiel Nutzereigenschaften) hinzufügen. Wenn Sie den Nutzertyp des aktuellen Nutzers an Ihre Analysebibliothek kommunizieren möchten, verwenden Sie SideEffect
, um seinen Wert zu aktualisieren.
@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.
Das System als zentrale Informationsquelle ansehen
Wenn das Ansichtssystem Inhaber des Status ist und ihn für „Compose“ freigibt, empfehlen wir, den Status in mutableStateOf
-Objekte zu verpacken, damit er Thread-sicher für Composer ist. Wenn Sie diesen Ansatz verwenden, werden zusammensetzbare Funktionen vereinfacht, da sie nicht mehr die „Source of Truth“ haben. Das View-System muss jedoch den änderbaren Status und die Ansichten, die diesen Status verwenden, aktualisieren.
Im folgenden Beispiel enthält ein CustomViewGroup
einen TextView
und einen ComposeView
mit einer zusammensetzbaren Funktion TextField
. Das TextView
muss den Inhalt dessen anzeigen, was der Nutzer in 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 in Compose als auch im Ansichtssystem gemeinsame UI-Elemente verwenden. Wenn Ihre Anwendung beispielsweise eine benutzerdefinierte CallToActionButton
-Komponente hat, müssen Sie sie möglicherweise sowohl auf Schreib- als auch auf ansichtsbasierten Bildschirmen verwenden.
In Compose werden freigegebene UI-Elemente zu zusammensetzbaren Funktionen, die in der gesamten Anwendung wiederverwendet werden können, unabhängig davon, ob das Element mit XML versehen ist oder eine benutzerdefinierte Ansicht ist. Sie erstellen beispielsweise eine zusammensetzbare Funktion CallToActionButton
für die benutzerdefinierte Call-to-Action-Komponente Button
.
Wenn Sie die zusammensetzbare Funktion in ansichtsbasierten Bildschirmen verwenden möchten, müssen Sie einen benutzerdefinierten Ansichts-Wrapper erstellen, der von AbstractComposeView
erweitert wird. Legen Sie in der überschriebenen zusammensetzbaren Funktion Content
die erstellte zusammensetzbare Funktion in Ihr Design „Compose“ ein, wie im folgenden Beispiel gezeigt:
@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 änderbaren Variablen. Dadurch wird die benutzerdefinierte Ansicht CallToActionViewButton
aufblasbar und nutzbar, ähnlich wie eine herkömmliche Ansicht. Im Folgenden finden Sie ein Beispiel hierfür mit der Ansichtsbindung:
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 */ } } } }
Enthält die benutzerdefinierte Komponente einen änderbaren Status, finden Sie weitere Informationen unter State Source of Truth.
Aufteilungsstatus der Präsentation priorisieren
Traditionell ist ein View
zustandsorientiert. Ein View
verwaltet neben der Anzeige auch Felder, die beschreiben, was angezeigt werden soll. Wenn Sie View
in Compose konvertieren, müssen die gerenderten Daten getrennt werden, um einen unidirektionalen Datenfluss zu erzielen, wie unter Zustandswinding näher erläutert.
Eine View
hat beispielsweise eine visibility
-Eigenschaft, die beschreibt, ob sie sichtbar, unsichtbar oder nicht mehr ist. Dies ist eine inhärente Eigenschaft von View
. Während andere Codeelemente die Sichtbarkeit einer View
ändern können, weiß nur die View
selbst wirklich, welche aktuelle Sichtbarkeit vorliegt. Die Logik, um dafür zu sorgen, dass eine View
sichtbar ist, kann fehleranfällig sein und ist häufig an das View
selbst gebunden.
Im Gegensatz dazu vereinfacht Compose mithilfe von bedingter Logik komplett verschiedene zusammensetzbare Funktionen in Kotlin:
@Composable fun MyComposable(showCautionIcon: Boolean) { if (showCautionIcon) { CautionIcon(/* ... */) } }
CautionIcon
muss nicht wissen, warum es angezeigt wird, und es gibt kein Konzept von visibility
: entweder ist es in der Komposition oder nicht.
Durch eine saubere Trennung von Statusverwaltung und Darstellungslogik können Sie die Anzeige von Inhalten als Konvertierung des Zustands in die UI freier anpassen. Die Möglichkeit, bei Bedarf Winden zu steuern, macht zusammensetzbare Funktionen auch wiederverwendbar, da die Zuständigkeit für den Zustand flexibler ist.
Gekapselte und wiederverwendbare Komponenten hochstufen
View
-Elemente haben oft eine Vorstellung davon, wo sie sich befinden: innerhalb einer Activity
-, Dialog
-, Fragment
- oder irgendwo innerhalb einer anderen View
-Hierarchie. Da sie häufig aus statischen Layoutdateien überlastet werden, ist die Gesamtstruktur einer View
in der Regel sehr starr. Dies führt zu einer engeren Kopplung und erschwert die Änderung oder Wiederverwendung einer View
.
Eine benutzerdefinierte View
kann beispielsweise davon ausgehen, dass sie eine untergeordnete Ansicht eines bestimmten Typs mit einer bestimmten ID hat, und ihre Eigenschaften direkt als Reaktion auf eine bestimmte Aktion ändert. Dadurch sind diese View
-Elemente eng miteinander verbunden: Das benutzerdefinierte View
-Element kann abstürzen oder nicht mehr funktionieren, wenn es das untergeordnete Element nicht findet. Das untergeordnete Element kann ohne das benutzerdefinierte übergeordnete Element View
wahrscheinlich nicht wiederverwendet werden.
Dies ist bei wiederverwendbaren zusammensetzbaren Funktionen in der Funktion „Schreiben“ weniger ein Problem. Übergeordnete Elemente können einfach Status und Callbacks angeben, sodass Sie wiederverwendbare zusammensetzbare Funktionen schreiben können, 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
braucht nur den aktuellenisEnabled
-Status zu kennen. Es ist nicht nötig zu wissen, obControlPanelWithToggle
existiert oder ob er steuerbar ist.ControlPanelWithToggle
weiß nicht, dassImageWithEnabledOverlay
existiert. Es kann null, eine oder mehrere Möglichkeiten geben, wieisEnabled
angezeigt wird undControlPanelWithToggle
muss sich nicht ändern.Für das übergeordnete Element spielt es keine Rolle, wie tief
ImageWithEnabledOverlay
oderControlPanelWithToggle
verschachtelt sind. Diese untergeordneten Elemente könnten Änderungen animieren, Inhalte austauschen oder Inhalte an andere Kinder weitergeben.
Dieses Muster wird als Umkehrung der Kontrolle bezeichnet. Weitere Informationen dazu finden Sie in der CompositionLocal
-Dokumentation.
Änderungen der Bildschirmgröße verarbeiten
Unterschiedliche Ressourcen für unterschiedliche Fenstergrößen sind eine der wichtigsten Möglichkeiten zum Erstellen responsiver View
-Layouts. Qualifizierte Ressourcen sind zwar weiterhin eine Option für Layoutentscheidungen auf Bildschirmebene, mit der Funktion „Compose“ ist es jedoch viel einfacher, Layouts vollständig im Code mit normaler bedingter Logik zu ändern. Weitere Informationen finden Sie unter Unterstützung verschiedener Bildschirmgrößen.
Unter Adaptive Layouts erstellen erfahren Sie außerdem, wie Sie in der Funktion „Compose“ adaptive UIs erstellen.
Verschachteltes Scrollen mit Ansichten
Weitere Informationen zum Aktivieren der verschachtelten Scroll-Interop zwischen scrollbaren View-Elementen und scrollbaren zusammensetzbaren Funktionen, die in beide Richtungen verschachtelt sind, finden Sie unter Verschachtelte Scroll-Interop.
In RecyclerView
verfassen
Zusammensetzbare Funktionen in RecyclerView
sind seit RecyclerView
-Version 1.3.0-alpha02 leistungsfähig. Du benötigst mindestens Version 1.3.0-alpha02 von RecyclerView
, um diese Vorteile sehen zu können.
WindowInsets
-Interoperabilität mit Datenansichten
Möglicherweise müssen Sie Standardeinfügungen überschreiben, wenn sich auf Ihrem Bildschirm sowohl Ansichten als auch Code in einer Hierarchie befinden. In diesem Fall müssen Sie genau angeben, in welcher Version die Einfügungen aufgenommen und in welchen ignoriert werden sollen.
Wenn Ihr äußerstes Layout beispielsweise ein Android View-Layout ist, sollten Sie die Einfügungen im View-System verwenden und für Compose ignorieren.
Wenn Ihr äußerstes Layout eine zusammensetzbare Funktion ist, sollten Sie die Einfügungen in Compose verwenden und die AndroidView
-zusammensetzbaren Funktionen entsprechend auffüllen.
Standardmäßig nutzt jeder ComposeView
alle Einsätze auf der Verbrauchsebene WindowInsetsCompat
. Wenn Sie dieses Standardverhalten ändern möchten, setzen Sie ComposeView.consumeWindowInsets
auf false
.
Weitere Informationen finden Sie in der Dokumentation zu WindowInsets
in Compose.
Empfehlungen für dich
- Hinweis: Der Linktext wird angezeigt, wenn JavaScript deaktiviert ist.
- Emojis anzeigen
- Material Design 2 in Compose
- Fenstereinfügungen in Compose