Die Migration von Views zu Compose ist zwar rein UI-bezogen, aber es gibt viele Dinge, die Sie beachten müssen, 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 ansichtenbasierte Apps sind drei Versionen von Material Design verfügbar:
- Material Design 1 mit der Bibliothek AppCompat (z.B.
Theme.AppCompat.*
) - Material Design 2 mit der Bibliothek MDC-Android (d.h.
Theme.MaterialComponents.*
) - Material Design 3 mit der Bibliothek MDC-Android (d.h.
Theme.Material3.*
)
Für Compose-Apps sind zwei Versionen von Material Design verfügbar:
- Material Design 2 mit der Bibliothek Compose Material (
androidx.compose.material.MaterialTheme
) - Material Design 3 mit der Bibliothek Compose Material 3 (z.B.
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 zu 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.) 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-Designthemen 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 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 (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 Architecture ComponentsViewModel
verwenden, können Sie von jedem Compose-Element aus auf eine ViewModel
zugreifen, indem Sie die Funktion viewModel()
aufrufen, wie unter Compose 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 für den Status
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 freigegebenen Status nach Möglichkeit in einer anderen Klasse zu kapseln, die den von beiden Plattformen verwendeten Best Practices für UDFs entspricht. Beispielsweise in einer ViewModel
, die einen Stream der freigegebenen Daten für die Ausgabe von Datenaktualisierungen bereitstellt.
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 die Quelle der Wahrheit sein und 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 den Befehl 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.
Mit Ihrer Analysebibliothek können Sie beispielsweise Ihre Nutzergruppe segmentieren, indem Sie allen nachfolgenden Analyseereignissen benutzerdefinierte Metadaten (in diesem Beispiel Nutzereigenschaften) anhängen. 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 änderbaren Status und die Ansichten aktualisieren, die diesen Status verwenden.
Im folgenden Beispiel enthält ein CustomViewGroup
ein TextView
und ein ComposeView
mit einem TextField
-Element. 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 Benutzeroberfläche migrieren
Wenn Sie nach und nach zu Compose migrieren, müssen Sie möglicherweise sowohl in 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 das von Ihnen erstellte Compose-Element in Ihrem Compose-Design in dem überschriebenen Content
-Element, 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 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.
Priorität des Splittstatus aus der Präsentation
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 die Eigenschaft visibility
, die angibt, ob es 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 können Sie dagegen ganz einfach mithilfe bedingter Logik in Kotlin völlig unterschiedliche Composeables 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. Wenn Sie den Status bei Bedarf hochladen können, sind auch Komponenten wiederverwendbarer, da die Zuweisung von Status 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 benutzerdefinierter View
kann beispielsweise davon ausgehen, dass er 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 verknüpft: 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 den aktuellenisEnabled
-Status kennen. Es muss nicht wissen, dassControlPanelWithToggle
existiert oder wie es gesteuert wird.ControlPanelWithToggle
weiß nicht, dassImageWithEnabledOverlay
existiert.isEnabled
kann auf null, eine oder mehrere Arten angezeigt 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 finden Sie in der CompositionLocal
-Dokumentation.
Änderungen der Bildschirmgröße verarbeiten
Unterschiedliche Ressourcen für unterschiedliche 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 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 Standard-Einzüge überschreiben, wenn sich auf Ihrem Bildschirm sowohl Ansichten als auch Compose-Code in derselben Hierarchie befinden. In diesem Fall müssen Sie angeben, welche der beiden Seiten die Einleger verwenden und welche sie ignorieren soll.
Wenn Ihr äußerstes Layout beispielsweise ein Android-View-Layout ist, sollten Sie die Einzüge im View-System verwenden und für Compose 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 von jedem ComposeView
alle Inset-Assets auf der Verbrauchsebene WindowInsetsCompat
verbraucht. 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“