Weitere Hinweise

Die Migration von Views zu Compose ist rein UI-bezogen, es gibt jedoch die für eine sichere und inkrementelle Migration berücksichtigt werden sollten. Dieses enthält einige Überlegungen zur Migration App zum Schreiben erstellen.

Design Ihrer App migrieren

Material Design ist das empfohlene Designsystem für das Erstellen 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 mithilfe der MDC – Android Bibliothek (z.B. Theme.MaterialComponents.*)
  • Material Design 3 mit der Bibliothek MDC-Android (d. h. Theme.Material3.*)

Für Editoren-Apps sind zwei Versionen von Material verfügbar:

  • Material Design 2 mithilfe der Bibliothek Compose Material (z.B. androidx.compose.material.MaterialTheme)
  • Material Design 3 mithilfe 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 in der Lage ist, dies zu tun. Für beide Ansichten sind Migrationsleitfäden verfügbar und Schreiben:

Wenn Sie neue Bildschirme in Compose erstellen, müssen Sie unabhängig von der verwendeten Version von Material Design eine MaterialTheme vor allen Composables anwenden, die UI aus den Materialbibliotheken von Compose ausgeben. Das Material Komponenten (Button, Text usw.) hängen davon ab, dass eine MaterialTheme vorhanden ist. und ihr Verhalten ist ohne sie nicht definiert.

Alle Jetpack Compose-Beispiele ein benutzerdefiniertes Design zum Verfassen von Nachrichten verwenden, das auf MaterialTheme basiert.

Weitere Informationen finden Sie unter Systeme in Compose und XML-Designs zu Compose migrieren.

Wenn Sie die Navigationskomponente in Ihrer App verwenden, lesen Sie Interoperabilität bei der Verwendung von Editoren und Weitere Informationen finden Sie unter Jetpack Navigation zu Navigation Compose migrieren.

Benutzeroberfläche für die kombinierte Ansicht „Compose“ und „Views“ testen

Nach der Migration von Teilen deiner App zu Compose sind Tests unerlässlich, nichts kaputt gemacht haben.

Wenn für eine Aktivität oder ein Fragment die Funktion „Schreiben“ verwendet wird, müssen Sie createAndroidComposeRule statt ActivityScenarioRule zu verwenden. createAndroidComposeRule-Integration ActivityScenarioRule durch einen ComposeTestRule, mit dem Sie die Funktionen Zur selben Zeit sehen Sie den Code.

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. Für Interoperabilität mit Frameworks für UI-Tests, siehe Interoperabilität mit Espresso und Interoperabilität mit UiAutomator

Compose in Ihre vorhandene Anwendungsarchitektur einbinden

UDF-Architektur (Unidirektionale Datenflüsse) Muster funktionieren nahtlos mit der Funktion „Schreiben“. Wenn die App andere Arten von Architekturmustern, wie Model View Presenter (MVP), empfehlen wir, Sie müssen diesen Teil der UI vor oder während der Einführung von „Compose“ zu UDF migrieren.

ViewModel in „Compose“ verwenden

Bei Verwendung der Architekturkomponenten ViewModel haben, können Sie auf ViewModel aus einer beliebigen zusammensetzbaren Funktion mit das Aufrufen der viewModel() wie unter Compose und andere Bibliotheken erläutert.

Achten Sie bei der Einführung von „Compose“ darauf, nicht denselben ViewModel-Typ in Verschiedene zusammensetzbare Funktionen, da ViewModel-Elemente den Bereichen des Ansichtslebenszyklus entsprechen. Die ist entweder die Hostaktivität, das Fragment oder das Navigationsdiagramm, Die Navigationsbibliothek wird verwendet.

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 zusammensetzbaren Funktionen.

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 bezieht sich der ViewModel auf den Lebenszyklus des Ziels und Sie wird gelöscht, wenn das Ziel aus dem Backstack entfernt wird. Im Beispiel: Wenn der Nutzer zur Seite Profil wechselt, wird eine neue Instanz von GreetingViewModel wird 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 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 und dieses System alle Datenaktualisierungen an das andere System weitergeben muss. 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“ verfassen

Verwenden Sie die Methode SideEffect zusammensetzbar, um den Erstellungsstatus in Nicht-Compose-Code zu veröffentlichen. In diesem Fall „Source of Truth“ wird in einer zusammensetzbaren Funktion gespeichert, die 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. Um den Nutzertyp der aktuellen Nutzer zu Ihrer Analysebibliothek hinzu, 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 beim Schreiben.

System als zentrale Datenquelle betrachten

Wenn das View-System Inhaber des Status ist und ihn für die Funktion „Schreiben“ freigibt, empfehlen wir, packen Sie den Status in mutableStateOf-Objekte ein, damit er Thread-sicher ist. Schreiben. Bei diesem Ansatz werden zusammensetzbare Funktionen vereinfacht, nicht mehr über die zentrale Informationsquelle, aber das View-System muss änderbaren Status und die Ansichten, die diesen Status verwenden.

Im folgenden Beispiel enthält ein CustomViewGroup einen TextView und einen ComposeView mit einer TextField zusammensetzbaren Funktion. Der TextView muss 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 Benutzeroberfläche migrieren

Wenn Sie schrittweise zu Compose migrieren, müssen Sie möglicherweise die gemeinsam genutzte UI verwenden in Compose und View. 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 gemeinsam genutzte UI-Elemente zu zusammensetzbaren Funktionen, die im gesamten unabhängig davon, ob das Element mit XML formatiert wurde oder es sich um eine benutzerdefinierte Ansicht handelt. Für Sie erstellen beispielsweise eine zusammensetzbare Funktion CallToActionButton für Ihren benutzerdefinierten Aufruf von Komponente „Aktion“ Button.

Um die zusammensetzbare Funktion in ansichtsbasierten Bildschirmen zu verwenden, erstellen Sie einen benutzerdefinierten Ansichts-Wrapper, geht vom AbstractComposeView an. In der überschriebenen zusammensetzbaren Content-Funktion platzieren Sie die von Ihnen erstellte zusammensetzbare Funktion in Ihrem Design "Schreiben", wie in der Beispiel unten:

@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 angezeigt wird. Dadurch ist die benutzerdefinierte CallToActionViewButton-Ansicht aufblasbar und nutzbar, wie eine traditionelle Ansicht. Ein Beispiel dafür finden Sie unter View Binding. unten:

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 Statusquelle der Wahrheit.

Aufteilungsstatus gegenüber Präsentation priorisieren

Traditionell ist ein View zustandsorientiert. Ein View verwaltet Felder, die beschreiben, was angezeigt werden soll und wie es angezeigt werden soll. Wenn Sie Konvertieren Sie View in „Compose“ und trennen Sie die gerenderten Daten Einen unidirektionalen Datenfluss erreichen, wie unter Zustandswinden näher erläutert.

Ein View hat beispielsweise ein visibility-Attribut, das beschreibt, sichtbar, unsichtbar oder weg. Dies ist eine inhärente Eigenschaft von View. Während Andere Codeteile können die Sichtbarkeit einer View ändern, nur die View die aktuelle Sichtbarkeit kennen. Die Logik, mit der sichergestellt wird, Ein View sichtbar ist, kann fehleranfällig sein und ist oft mit dem View verknüpft selbst.

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 oder sich darum kümmern, warum es angezeigt wird, und es gibt kein Konzept von visibility: Es ist entweder in der Komposition nicht.

Durch eine klare Trennung der Statusverwaltung und der Präsentationslogik können Sie die Darstellung von Inhalten als Umwandlung des Zustands in die Benutzeroberfläche beliebig ändern. Wenn Sie den Status bei Bedarf hochladen können, sind auch Komponenten wiederverwendbarer, da die Zuweisung von Status flexibler ist.

Gekapselte und wiederverwendbare Komponenten hochstufen

View-Elemente haben oft eine Vorstellung davon, wo sie sich befinden: innerhalb einer Activity, einem Dialog, eine Fragment oder irgendwo innerhalb einer anderen View-Hierarchie. Weil Sie werden oft von statischen Layoutdateien überhöht, denn die Gesamtstruktur eines View ist in der Regel sehr starr. Dies führt zu einer engeren Kopplung und macht es schwieriger ist, dass ein View geändert oder wiederverwendet werden kann.

Ein benutzerdefiniertes View kann beispielsweise davon ausgehen, dass es eine untergeordnete Ansicht eines bestimmten mit einer bestimmten ID eingeben und die zugehörigen Eigenschaften direkt Aktion ausführen. Dadurch werden diese View-Elemente eng miteinander verbunden: das benutzerdefinierte View-Element. Das Gerät stürzt ab oder ist kaputt, wenn das Kind das Kind nicht finden kann und es wahrscheinlich nicht ohne das benutzerdefinierte übergeordnete Element View wiederverwendet werden.

Mit wiederverwendbaren Compose-Elementen ist das in Compose weniger ein Problem. Eltern können geben Sie ganz einfach Status und Callbacks an, sodass Sie wiederverwendbare zusammensetzbare Funktionen ohne 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, wie die aktuelle isEnabled ist. Es muss nicht wissen, dass ControlPanelWithToggle existiert, oder sogar wie es steuerbar ist.

  • ControlPanelWithToggle weiß nicht, dass ImageWithEnabledOverlay existiert. Es kann null, eine oder mehrere Möglichkeiten zur Anzeige von isEnabled geben. ControlPanelWithToggle muss nicht geändert werden.

  • Für das übergeordnete Element spielt es keine Rolle, wie tief verschachtelt ImageWithEnabledOverlay ist. oder ControlPanelWithToggle. Diese Kinder könnten Veränderungen animieren, den Austausch von Inhalten oder die Weitergabe von Inhalten an andere Kinder.

Dieses Muster wird als Inversion of Control bezeichnet. Weitere Informationen dazu finden Sie weiter unten. finden Sie in der CompositionLocal-Dokumentation.

Umgang mit Änderungen der Bildschirmgröße

Unterschiedliche Ressourcen für unterschiedliche Fenstergrößen ist eine der wichtigsten Möglichkeiten, responsive View-Layouts erstellen. Qualifizierte Ressourcen sind zwar weiterhin eine Option, für Layoutentscheidungen auf Bildschirmebene vereinfacht, ganz im Code mit normaler bedingter Logik. Weitere Informationen finden Sie unter Fenstergrößenklassen.

Weitere Informationen finden Sie im Hilfeartikel Unterstützung verschiedener Bildschirmgrößen. erfahren Sie, wie Sie mit Compose adaptive UIs erstellen.

Verschachteltes Scrollen mit Ansichten

Weitere Informationen zum Aktivieren der verschachtelten Scrolling-Interoperabilität zwischen scrollable View-Elemente und scrollbarer zusammensetzbarer Funktionen, die in beide Richtungen verschachtelt sind. Lesen Verschachtelte Scroll-Interoperabilität:

In RecyclerView schreiben

Zusammensetzbare Funktionen in RecyclerView sind seit Version RecyclerView leistungsfähig 1.3.0-alpha02. Stellen Sie sicher, dass Sie mindestens Version 1.3.0-alpha02 von RecyclerView, um diese Vorteile zu sehen.

WindowInsets-Interoperabilität mit Ansichten

Möglicherweise müssen Sie die Standardeinfügungen überschreiben, wenn Ihr Bildschirm sowohl Ansichten als auch Erstellen Sie Code in derselben Hierarchie. 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 es sich bei Ihrem äußersten Layout um eine zusammensetzbare Funktion handelt, sollten Sie das Element Einfügungen in Compose und füllen Sie die AndroidView zusammensetzbaren Funktionen entsprechend aus.

Standardmäßig verbraucht jeder ComposeView alle Einfügungen an der WindowInsetsCompat Verbrauchslevel. Wenn Sie dieses Standardverhalten ändern möchten, setzen Sie ComposeView.consumeWindowInsets auf false.

Weitere Informationen finden Sie unter WindowInsets in Compose.