Die Migration von Views zu Compose ist zwar rein UI-bezogen, es gibt aber viele Dinge, die bei einer sicheren und inkrementellen Migration berücksichtigt werden müssen. Auf dieser Seite finden Sie einige Überlegungen zur Migration Ihrer View-basierten App zu Compose.
App-Design migrieren
Material Design ist das empfohlene Designsystem für das Theming von Android-Apps.
Für ansichtsbasierte Apps sind drei Versionen von Material verfügbar:
- Material Design 1 mit der AppCompat-Bibliothek (d.h.
Theme.AppCompat.*
) - Material Design 2 mit der MDC-Android-Bibliothek (d.h.
Theme.MaterialComponents.*
) - Material Design 3 mit der MDC-Android-Bibliothek (d.h.
Theme.Material3.*
)
Für Compose-Apps sind zwei Versionen von Material verfügbar:
- Material Design 2 mit der Compose Material-Bibliothek (d.h.
androidx.compose.material.MaterialTheme
) - Material 3-Design mit der Compose Material 3-Bibliothek (d.h.
androidx.compose.material3.MaterialTheme
)
Wir empfehlen, die aktuelle Version (Material 3) zu verwenden, wenn das Designsystem Ihrer App dies zulässt. Es sind Migrationsleitfäden für Views und Compose verfügbar:
- Material 1 bis 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 davon, welche Version von Material Design Sie verwenden, vor allen Composables, die UI aus den Compose Material-Bibliotheken ausgeben, ein MaterialTheme
anwenden. Die Material-Komponenten (Button
, Text
usw.) sind von einem MaterialTheme
abhängig 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 Designsysteme in Compose und XML-Themes zu Compose migrieren.
Navigation
Wenn Sie die Navigationskomponente in Ihrer App verwenden, finden Sie weitere Informationen unter Mit Compose navigieren – Interoperabilität und Jetpack Navigation zu Navigation Compose migrieren.
Gemischte Compose-/Views-UI testen
Nachdem Sie Teile Ihrer App zu Compose migriert haben, ist es wichtig, sie zu testen, um sicherzugehen, dass nichts beschädigt wurde.
Wenn eine Aktivität oder ein Fragment Compose verwendet, müssen Sie createAndroidComposeRule
anstelle von ActivityScenarioRule
verwenden. createAndroidComposeRule
integriert ActivityScenarioRule
mit einem ComposeTestRule
, mit dem Sie Compose- und View-Code 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 zum Testen finden Sie unter Compose-Layout 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 (Unidirectional Data Flow, UDF) funktionieren nahtlos mit Compose. Wenn die App stattdessen andere Architekturmuster wie Model View Presenter (MVP) verwendet, empfehlen wir, diesen Teil der Benutzeroberfläche vor oder während der Einführung von Compose zu UDF zu migrieren.
ViewModel
in Compose verwenden
Wenn Sie die Architecture Components-Bibliothek ViewModel
verwenden, können Sie über die Funktion viewModel()
auf ein ViewModel
aus jedem Composable zugreifen, wie unter Compose und andere Bibliotheken beschrieben.
Wenn Sie Compose verwenden, sollten Sie darauf achten, nicht denselben ViewModel
-Typ in verschiedenen Composables zu verwenden, da ViewModel
-Elemente View-Lifecycle-Bereichen folgen. Der Bereich ist entweder die Host-Aktivität, das Fragment oder der Navigationsgraph, wenn die Navigationsbibliothek verwendet wird.
Wenn die Composables 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 Composables unter der Hostaktivität wiederverwendet wird. Die erste erstellte ViewModel
-Instanz wird in anderen Composables 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 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. 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“ für den Status
Wenn Sie Compose in einem Teil der Benutzeroberfläche verwenden, müssen sich Compose und der View-Systemcode möglicherweise Daten teilen. Wenn möglich, empfehlen wir, diesen gemeinsamen Status in einer anderen Klasse zu kapseln, die den UDF-Best Practices beider Plattformen entspricht, z. B. in einem ViewModel
, das einen Stream der freigegebenen Daten bereitstellt, um Datenaktualisierungen auszugeben.
Das ist jedoch nicht immer möglich, wenn die freizugebenden Daten veränderlich oder eng an ein UI-Element gebunden sind. In diesem Fall muss ein System die Quelle der Wahrheit sein und alle Datenaktualisierungen an das andere System weitergeben. Als Faustregel gilt, dass die „Source of Truth“ dem Element gehören sollte, das sich näher an der Wurzel der UI-Hierarchie befindet.
Compose als „Source of Truth“
Mit der SideEffect
-Composable können Sie den Compose-Status in Nicht-Compose-Code veröffentlichen. In diesem Fall wird die Quelle der Wahrheit in einem zusammensetzbaren Element beibehalten, das Statusaktualisierungen sendet.
Mit Ihrer Analysebibliothek können Sie Ihre Nutzer beispielsweise segmentieren, indem Sie allen nachfolgenden Analyseereignissen benutzerdefinierte Metadaten (in diesem Beispiel Nutzereigenschaften) anhängen. Wenn Sie den Nutzertyp des aktuellen Nutzers an Ihre Analysenbibliothek übermitteln möchten, verwenden Sie SideEffect
, um den 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.
System als „Source of Truth“ ansehen
Wenn das View-System den Status besitzt und ihn mit Compose teilt, empfehlen wir, den Status in mutableStateOf
-Objekte einzuschließen, damit er für Compose threadsicher ist. Wenn Sie diesen Ansatz verwenden, werden zusammensetzbare Funktionen vereinfacht, da sie nicht mehr die Quelle der Wahrheit sind. Das View-System muss jedoch den veränderlichen Status und die Views, die diesen Status verwenden, aktualisieren.
Im folgenden Beispiel enthält ein CustomViewGroup
ein TextView
und ein ComposeView
mit einem TextField
-Composable. Im TextView
muss der Inhalt des Textes zu sehen sein, den der Nutzer in das 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 } }
Gemeinsame Benutzeroberfläche migrieren
Wenn Sie nach und nach zu Compose migrieren, müssen Sie möglicherweise gemeinsame UI-Elemente sowohl in Compose als auch im View-System verwenden. Wenn Ihre App beispielsweise eine benutzerdefinierte CallToActionButton
-Komponente hat, müssen Sie sie möglicherweise sowohl in Compose- als auch in View-basierten Bildschirmen verwenden.
In Compose werden freigegebene UI-Elemente zu Composables, die in der gesamten App wiederverwendet werden können, unabhängig davon, ob das Element mit XML formatiert wurde oder eine benutzerdefinierte Ansicht ist. Sie erstellen beispielsweise ein CallToActionButton
-Composable für Ihre benutzerdefinierte Call-to-Action-Komponente Button
.
Wenn Sie das Composable in bildschirmbasierten Ansichten verwenden möchten, erstellen Sie einen benutzerdefinierten Ansichtsumbruch, der von AbstractComposeView
abgeleitet wird. Platzieren Sie die erstellte Composable in der überschriebenen Content
-Composable, die in Ihrem Compose-Theme umschlossen ist, wie im Beispiel unten 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) } } }
Beachten Sie, dass die zusammensetzbaren Parameter innerhalb der benutzerdefinierten Ansicht zu veränderlichen Variablen werden. Dadurch wird die benutzerdefinierte CallToActionViewButton
-Ansicht aufgeblasen und kann wie eine herkömmliche Ansicht verwendet werden. Hier finden Sie ein Beispiel für View Binding:
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änderlichen Status enthält, lesen Sie den Abschnitt State source of truth.
Zustand und Darstellung trennen
Traditionell ist ein View
zustandsorientiert. Ein View
verwaltet Felder, die beschreiben, was angezeigt werden soll, zusätzlich dazu, wie es angezeigt werden soll. Wenn Sie ein View
in Compose umwandeln, sollten Sie die gerenderten Daten trennen, um einen unidirektionalen Datenfluss zu erreichen. Weitere Informationen finden Sie unter State Hoisting.
Ein View
-Objekt hat beispielsweise das Attribut visibility
, das beschreibt, ob es sichtbar, unsichtbar oder nicht vorhanden ist. Dies ist eine inhärente Eigenschaft des View
. Andere Codeabschnitte können zwar die Sichtbarkeit eines View
ändern, aber nur das View
selbst weiß, wie seine aktuelle Sichtbarkeit ist. Die Logik, mit der sichergestellt wird, dass ein View
sichtbar ist, kann fehleranfällig sein und ist oft an das View
selbst gebunden.
Mit Compose ist es dagegen ganz einfach, mit bedingter Logik in Kotlin völlig unterschiedliche Composables anzuzeigen:
@Composable fun MyComposable(showCautionIcon: Boolean) { if (showCautionIcon) { CautionIcon(/* ... */) } }
CautionIcon
muss nicht wissen oder sich darum kümmern, warum es angezeigt wird. Es gibt kein Konzept von visibility
: Es ist entweder in der Komposition enthalten oder nicht.
Durch die saubere Trennung von Statusverwaltung und Darstellungslogik können Sie freier ändern, wie Inhalte als Umwandlung von Status in die Benutzeroberfläche angezeigt werden. Wenn Sie den Status bei Bedarf nach oben verschieben können, sind Composables auch besser wiederverwendbar, da die Statusinhaberschaft flexibler ist.
Gekapselte und wiederverwendbare Komponenten fördern
View
-Elemente haben oft eine Vorstellung davon, wo sie sich befinden: in einem Activity
, einem Dialog
, einem Fragment
oder irgendwo in einer anderen View
-Hierarchie. Da sie häufig aus statischen Layoutdateien erstellt werden, ist die Gesamtstruktur eines 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 werden diese View
-Elemente eng miteinander verknüpft: Das benutzerdefinierte View
kann abstürzen oder beschädigt werden, wenn das untergeordnete Element nicht gefunden wird, und das untergeordnete Element kann wahrscheinlich nicht ohne das benutzerdefinierte View
-Übergeordnete Element wiederverwendet werden.
Bei der Verwendung von wiederverwendbaren Composables in Compose ist das weniger ein Problem. Eltern können ganz einfach Status und Callbacks angeben, sodass Sie wiederverwendbare Composables schreiben können, ohne genau wissen zu müssen, 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 den aktuellenisEnabled
-Status kennen. Es muss nicht wissen, dassControlPanelWithToggle
vorhanden ist oder wie es gesteuert werden kann.ControlPanelWithToggle
weiß nicht, dassImageWithEnabledOverlay
existiert.isEnabled
kann auf null, eine oder mehrere Arten dargestellt werden undControlPanelWithToggle
muss 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 dazu finden Sie in der CompositionLocal
-Dokumentation.
Änderungen der Displaygröße verarbeiten
Verschiedene Ressourcen für verschiedene Fenstergrößen sind eine der wichtigsten Möglichkeiten, responsive View
-Layouts zu erstellen. Qualifizierte Ressourcen sind zwar weiterhin eine Option für Layoutentscheidungen auf Bildschirmebene, mit Compose ist es jedoch viel einfacher, Layouts vollständig im Code mit normaler bedingter Logik zu ändern. Weitere Informationen finden Sie unter Klassen für Fenstergrößen verwenden.
Weitere Informationen zu den Techniken, die Compose zum Erstellen adaptiver UIs bietet, findest du hier.
Verschachteltes Scrollen mit Views
Weitere Informationen zum Aktivieren der Interoperabilität von verschachteltem Scrollen zwischen scrollbaren View-Elementen und scrollbaren Composables, die in beide Richtungen verschachtelt sind, finden Sie unter Interoperabilität von verschachteltem Scrollen.
E‑Mail in RecyclerView
schreiben
Composables in RecyclerView
sind seit RecyclerView
-Version 1.3.0-alpha02 leistungsstark. Sie müssen mindestens Version 1.3.0-alpha02 von RecyclerView
verwenden, um diese Vorteile nutzen zu können.
WindowInsets
Interop mit Views
Möglicherweise müssen Sie die Standard-Insets überschreiben, wenn Ihr Bildschirm sowohl Views als auch Compose-Code in derselben Hierarchie enthält. In diesem Fall müssen Sie explizit angeben, welche Ansicht die Insets verwenden und welche sie ignorieren soll.
Wenn Ihr äußerstes Layout beispielsweise ein Android-View-Layout ist, sollten Sie die Insets im View-System verwenden und sie für Compose ignorieren.
Wenn Ihr äußerstes Layout ein Composable ist, sollten Sie die Insets in Compose verwenden und die AndroidView
-Composables entsprechend auffüllen.
Standardmäßig werden für jede ComposeView
alle Insets auf der Verbrauchsebene WindowInsetsCompat
verwendet. Wenn Sie dieses Standardverhalten ändern möchten, legen Sie ComposeView.consumeWindowInsets
auf false
fest.
Weitere Informationen finden Sie in der Dokumentation zu WindowInsets
in Compose.
Empfehlungen für dich
- Hinweis: Linktext wird angezeigt, wenn JavaScript deaktiviert ist.
- Emojis anzeigen
- Material Design 2 in Compose
- Fenstereinsetzungen in Compose