Die Migration von Ansichten zu Compose betrifft zwar nur die Benutzeroberfläche, es gibt jedoch viele Dinge, die Sie beachten müssen, um eine sichere und inkrementelle Migration durchzuführen. Auf dieser Seite finden Sie einige Überlegungen zur Migration Ihrer View-basierten App zu Compose.
Design der App 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 Design 3 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 gibt Migrationsanleitungen für Views und Compose:
- Material 1 zu Material 2 in Ansichten
- Material 2 zu Material 3 in Ansichten
- Von Material 2 zu Material 3 in Compose
Wenn Sie in Compose neue Bildschirme erstellen, müssen Sie unabhängig von der verwendeten Material Design-Version ein MaterialTheme vor allen Composables anwenden, die UI aus den Compose Material-Bibliotheken ausgeben. Die Material-Komponenten (Button, Text usw.) hängen von einem vorhandenen MaterialTheme ab 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-Designs zu Compose migrieren.
Navigation
Wenn Sie die Navigationskomponente in Ihrer App verwenden, finden Sie weitere Informationen unter In einer fragmentbasierten App von Compose aus navigieren 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 sicherzustellen, dass alles funktioniert.
Wenn eine Aktivität oder ein Fragment Compose verwendet, müssen Sie createAndroidComposeRule anstelle von ActivityScenarioRule verwenden. createAndroidComposeRule integriert ActivityScenarioRule in ein 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 zu Tests 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 den unidirektionalen Datenfluss (UDF) funktionieren nahtlos mit Compose. Wenn die App stattdessen andere Arten von Architekturmustern 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 Bibliothek Architekturkomponenten ViewModel verwenden, können Sie von jeder zusammensetzbaren Funktion aus auf ein ViewModel zugreifen, indem Sie die Funktion viewModel() aufrufen, wie unter Compose und andere Bibliotheken erläutert.
Wenn Sie Compose verwenden, sollten Sie darauf achten, nicht denselben ViewModel-Typ in verschiedenen Composables zu verwenden, da ViewModel-Elemente den Lebenszyklusbereichen von Ansichten folgen. Der Bereich ist entweder die Hostaktivität, das Fragment oder das Navigationsdiagramm, 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 abgeschlossen 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 den Bereich von ViewModel-Elementen festlegen, haben Composables, die ein Ziel in einem Navigationsgraphen sind, eine andere Instanz des ViewModel.
In diesem Fall ist der 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 zum Bildschirm Profil wechselt, wird eine neue Instanz von GreetingViewModel erstellt.
@Composable fun MyApp() { NavHost(rememberNavController(), startDestination = "profile/{userId}") { /* ... */ composable("profile/{userId}") { backStackEntry -> GreetingScreen(backStackEntry.arguments?.getString("userId") ?: "") } } }
Zentrale Datenquelle für den Status
Wenn Sie Compose in einem Teil der Benutzeroberfläche verwenden, müssen Compose und der Code des View-Systems möglicherweise Daten gemeinsam nutzen. Wir empfehlen, diesen gemeinsamen Zustand nach Möglichkeit in einer anderen Klasse zu kapseln, die den Best Practices für den unidirektionalen Datenfluss folgt, die von beiden Plattformen verwendet werden, z. B. in einem ViewModel, der einen Stream der freigegebenen Daten bereitstellt, um Datenaktualisierungen auszugeben.
Das ist jedoch nicht immer möglich, wenn die zu teilenden Daten veränderlich oder eng an ein UI-Element gebunden sind. In diesem Fall muss ein System die „Source of Truth“ sein und alle Datenaktualisierungen an das andere System weitergeben. Als allgemeine Faustregel gilt, dass die „Source of Truth“ dem Element gehören sollte, das sich am nächsten am Stamm der UI-Hierarchie befindet.
Compose als „Source of Truth“
Verwenden Sie die komponierbare Funktion SideEffect, um den Compose-Zustand in Code zu veröffentlichen, der nicht auf Compose basiert. In diesem Fall wird die „Source of Truth“ in einer zusammensetzbaren Funktion gespeichert, die Statusaktualisierungen sendet.
Beispielsweise können Sie mit Ihrer Analysebibliothek Ihre Nutzerpopulation segmentieren, indem Sie allen nachfolgenden Analyseereignissen benutzerdefinierte Metadaten (in diesem Beispiel Nutzereigenschaften) anhängen. Wenn Sie den Nutzertyp des aktuellen Nutzers an Ihre Analytikbibliothek ü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 Nebenwirkungen in Compose.
System als „Source of Truth“ ansehen
Wenn das View-System den Status besitzt und mit Compose teilt, empfehlen wir, den Status in mutableStateOf-Objekte zu verpacken, um ihn für Compose threadsicher zu machen. 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 aktualisieren, die diesen Status verwenden.
Im folgenden Beispiel enthält ein CustomViewGroup ein TextView und ein ComposeView mit einem darin enthaltenen TextField-Composable. Die TextView muss den Inhalt dessen anzeigen, was 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 } }
Gemeinsame Benutzeroberfläche migrieren
Wenn Sie schrittweise zu Compose migrieren, müssen Sie möglicherweise freigegebene 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 ansichtsbasierten 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 ist oder eine benutzerdefinierte Ansicht ist. Sie würden beispielsweise eine CallToActionButton komponierbare Funktion für Ihre benutzerdefinierte Call-to-Action-Komponente Button erstellen.
Wenn du die komponierbare Funktion in ansichtsbasierten Bildschirmen verwenden möchtest, erstelle einen benutzerdefinierten Ansichts-Wrapper, der von AbstractComposeView abgeleitet ist. Platzieren Sie in der überschriebenen Content-Komposition das von Ihnen erstellte Composable, das in Ihr Compose-Design eingebunden ist, 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) } } }
Beachte, dass die zusammensetzbaren Parameter in der benutzerdefinierten Ansicht zu veränderlichen Variablen werden. Dadurch wird die benutzerdefinierte CallToActionViewButton-Ansicht wie eine herkömmliche Ansicht aufblähbar und verwendbar. Hier ein Beispiel mit 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 Source of Truth für den Status.
Priorisieren Sie die Trennung von Status und Präsentation.
Traditionell ist ein View zustandsorientiert. Ein View verwaltet Felder, die beschreiben, was angezeigt werden soll, und wie es angezeigt werden soll. Wenn Sie ein View in Compose konvertieren, sollten Sie die gerenderten Daten trennen, um einen unidirektionalen Datenfluss zu erreichen, wie unter Status-Hoisting erläutert.
Beispielsweise hat ein View-Objekt das Attribut visibility, das angibt, 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 der View selbst kennt seine aktuelle Sichtbarkeit. Die Logik, mit der sichergestellt wird, dass ein View sichtbar ist, kann fehleranfällig sein und ist oft an den View selbst gebunden.
Im Gegensatz dazu können Sie mit Compose ganz einfach völlig unterschiedliche Composables mithilfe von 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 von visibility: Es ist entweder in der Composition oder nicht.
Durch die saubere Trennung von Statusverwaltung und Präsentationslogik können Sie die Darstellung von Inhalten als Umwandlung von Status in UI freier ändern. Wenn Sie den Status bei Bedarf hochstufen 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 oft aus statischen Layoutdateien aufgebläht werden, ist die Gesamtstruktur eines View in der Regel sehr starr. Dies führt zu einer engeren Kopplung und erschwert das Ändern oder Wiederverwenden eines View.
Beispielsweise kann eine benutzerdefinierte View davon ausgehen, dass sie eine untergeordnete Ansicht eines bestimmten Typs mit einer bestimmten ID hat, und ihre Eigenschaften direkt als Reaktion auf eine Aktion ändern. Dadurch werden diese View-Elemente eng miteinander gekoppelt: Das benutzerdefinierte View-Element kann abstürzen oder fehlerhaft sein, wenn es das untergeordnete Element nicht findet, und das untergeordnete Element kann wahrscheinlich nicht ohne das benutzerdefinierte übergeordnete View-Element wiederverwendet werden.
In Compose ist das dank wiederverwendbarer Composables weniger ein Problem. Übergeordnete Elemente können Status und Callbacks einfach angeben, sodass Sie wiederverwendbare Composables schreiben können, ohne den genauen Ort zu kennen, an dem 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:
ImageWithEnabledOverlaymuss nur wissen, wie der aktuelleisEnabled-Status ist. Es muss nicht wissen, dassControlPanelWithToggleexistiert oder wie es gesteuert werden kann.ControlPanelWithToggleweiß nicht, dassImageWithEnabledOverlayexistiert. Es kann null, eine oder mehrere Möglichkeiten geben, wieisEnabledangezeigt wird, undControlPanelWithTogglemüsste sich nicht ändern.Für das übergeordnete Element spielt es keine Rolle, wie tief
ImageWithEnabledOverlayoderControlPanelWithToggleverschachtelt 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.
Umgang mit Änderungen der Bildschirmgröße
Die Verwendung unterschiedlicher Ressourcen für verschiedene Fenstergrößen ist eine der wichtigsten Methoden, um responsive View-Layouts zu erstellen. Qualifizierte Ressourcen sind zwar immer noch eine Option für Layoutentscheidungen auf Bildschirmebene, aber mit Compose ist es viel einfacher, Layouts vollständig im Code mit normaler bedingter Logik zu ändern. Weitere Informationen finden Sie unter Fenstergrößenklassen verwenden.
Weitere Informationen zu den Techniken, die Compose zum Erstellen adaptiver Benutzeroberflächen bietet, finden Sie unter Verschiedene Displaygrößen unterstützen.
Verschachteltes Scrollen mit Ansichten
Weitere Informationen zum Aktivieren der Interoperabilität für verschachteltes Scrollen zwischen scrollbaren Ansichtselementen und scrollbaren zusammensetzbaren Funktionen, die in beide Richtungen verschachtelt sind, finden Sie unter Interoperabilität für verschachteltes Scrollen.
In RecyclerView verfassen
Kompositionen in RecyclerView sind seit der RecyclerView-Version 1.3.0-alpha02 leistungsstark. Sie müssen mindestens die Version 1.3.0-alpha02 von RecyclerView verwenden, um diese Vorteile nutzen zu können.
WindowInsets-Interoperabilität mit Ansichten
Möglicherweise müssen Sie die Standard-Insets überschreiben, wenn Ihr Bildschirm sowohl Ansichten als auch Compose-Code in derselben Hierarchie enthält. In diesem Fall müssen Sie explizit angeben, welches Element die Insets verwenden und welches sie ignorieren soll.
Wenn Ihr äußerstes Layout beispielsweise ein Android-Ansichtslayout ist, sollten Sie die Insets im Ansichtssystem nutzen und für Compose ignorieren.
Wenn Ihr äußerstes Layout ein Composable ist, sollten Sie die Insets in Compose verwenden und die AndroidView-Composables entsprechend mit Padding versehen.
Standardmäßig verbraucht jedes ComposeView alle Insets 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 Sie
- Hinweis: Der Linktext wird angezeigt, wenn JavaScript deaktiviert ist.
- Emoji anzeigen
- Material Design 2 in Compose
- Fenstereinsätze in Compose