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 ansichtsbasierte Apps sind drei Material-Versionen verfügbar:

  • Material Design 1 mit dem AppCompat-Bibliothek (z.B. Theme.AppCompat.*)
  • Material Design 2 mithilfe der MDC – Android Bibliothek (z.B. Theme.MaterialComponents.*)
  • Material Design 3 mithilfe der MDC – Android Bibliothek (z.B. 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:

beim Erstellen neuer Bildschirme in „Compose“, unabhängig von der Materialversion Verwenden Sie für das von Ihnen verwendete Design MaterialTheme vor allen zusammensetzbare Funktionen, die Benutzeroberflächen aus den Compose Material Libraries 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.

Gemischte Benutzeroberfläche zum Schreiben und Ansehen 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) verwenden, 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() enthalten, 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 zusammensetzbaren Funktionen beispielsweise in einer Aktivität gehostet werden, ist viewModel() immer gibt 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, weil dieselbe GreetingViewModel-Instanz in allen zusammensetzbaren Funktionen unter der Host-Aktivitäten. 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 Navigationsdiagramme auch ViewModel-Elemente abdecken, Ziel in einem Navigationsdiagramm eine andere Instanz von ViewModel haben. 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 die Funktion „Verfassen“ in einem Bereich der Benutzeroberfläche verwenden, kann es vorkommen, Daten des Systemcodes der Ansicht freigeben müssen. Wenn möglich, empfehlen wir Ihnen, den gemeinsamen Status in einer anderen Klasse kapseln, die den Best Practices für UDFs entspricht von beiden Plattformen genutzt werden. Beispiel: In einer ViewModel, die einen Stream des freigegebene Daten, um Datenaktualisierungen durchzuführen.

Dies ist jedoch nicht immer möglich, wenn die freizugebenden Daten veränderlich oder sind eng mit einem UI-Element verbunden. In diesem Fall muss ein System die Quelle und dieses System alle Datenaktualisierungen an das andere System weitergeben muss. Als allgemeine Faustregel: Die "Source of Truth" sollte dem Element gehören, liegt näher an der Untergrenze der UI-Hierarchie.

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.

Ihre Analysebibliothek bietet Ihnen beispielsweise die Möglichkeit, Nutzer zu segmentieren, durch Hinzufügen benutzerdefinierter Metadaten (in diesem Beispiel Nutzereigenschaften) auf alle nachfolgenden Analyseereignisse anwenden. 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 UI 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 müssen Sie sie möglicherweise sowohl in der E-Mail-Funktion und ansichtsbasierte Bildschirme.

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 Ansicht. 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 änderbaren Status enthält, lesen Sie den Abschnitt Quelle des Status von 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 der Funktion „Schreiben“ lassen sich dagegen ganz unterschiedliche zusammensetzbare Funktionen mit bedingter Logik in Kotlin:

@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. Das die bei Bedarf winden können, machen Zusammensetzbare auch besser wiederverwendbar, ist flexibler.

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.

Dies ist in der Funktion „Mit wiederverwendbaren zusammensetzbaren Funktionen verfassen“ weniger problematisch. 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 explizit welches die Insets aufnehmen und welches sie ignorieren soll.

Wenn das äußerste Layout zum Beispiel ein Android View-Layout ist, sollten Sie die Einsätze im View-System verarbeiten und beim Schreiben 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. Um dieses Standardverhalten zu ändern, legen Sie Folgendes fest: ComposeView.consumeWindowInsets an false.

Weitere Informationen finden Sie unter WindowInsets in Compose.