Empfehlungen für die Android-Architektur

Auf dieser Seite finden Sie verschiedene Best Practices und Empfehlungen zur Architektur. Setzen Sie sie ein, um die Qualität, Robustheit und Skalierbarkeit Ihrer App zu verbessern. Sie erleichtern außerdem die Wartung und das Testen Ihrer App.

Die folgenden Best Practices sind nach Themen gruppiert. Jede Priorität hat eine Priorität, die angibt, wie stark das Team sie empfiehlt. Die Liste der Prioritäten sieht so aus:

  • Dringend empfohlen: Sie sollten diese Vorgehensweise implementieren, es sei denn, sie steht im Widerspruch zu Ihrem Ansatz grundlegend zur Verfügung.
  • Empfohlen:Damit können Sie Ihre App wahrscheinlich verbessern.
  • Optional:Durch diese Vorgehensweise kann Ihre App unter bestimmten Umständen verbessert werden.

Mehrschichtige Architektur

Unsere empfohlene mehrschichtige Architektur bevorzugt eine Trennung der Problembereiche. Sie stützt die UI aus Datenmodellen, entspricht dem Single-Source-of-Truth-Prinzip und folgt den Prinzipien des unidirektionalen Datenflusses. Hier sind einige Best Practices für mehrschichtige Architekturen:

Empfehlung Beschreibung
Verwenden Sie eine klar definierte Datenschicht.
Dringend empfohlen
Die Datenschicht stellt Anwendungsdaten für den Rest der App bereit und enthält den Großteil der Geschäftslogik Ihrer App.
  • Sie sollten Repositories erstellen, auch wenn sie nur eine einzige Datenquelle enthalten.
  • In kleinen Apps können Sie Datenschichttypen in einem data-Paket oder -Modul platzieren.
Verwenden Sie eine klar definierte UI-Ebene.
Dringend empfohlen
Die UI-Ebene zeigt die Anwendungsdaten auf dem Bildschirm an und dient als primärer Punkt für die Nutzerinteraktion.
  • In kleinen Apps können Sie Datenschichttypen in einem ui-Paket oder -Modul platzieren.
Weitere Best Practices für UI-Ebenen
Die Datenschicht sollte Anwendungsdaten mithilfe eines Repositorys bereitstellen.
Dringend empfohlen

Komponenten auf der UI-Ebene wie zusammensetzbare Funktionen, Aktivitäten oder ViewModels sollten nicht direkt mit einer Datenquelle interagieren. Beispiele für Datenquellen:

  • Datenbanken, DataStore, SharedPreferences, Firebase APIs.
  • GPS-Standortanbieter
  • Bluetooth-Datenanbieter.
  • Anbieter für den Status der Netzwerkverbindung.
Verwenden Sie Koroutinen und Abläufe.
Dringend empfohlen
Verwenden Sie Koroutinen und Abläufe für die Kommunikation zwischen Layern.

Weitere Best Practices für Koroutinen

Verwenden Sie eine Domainebene.
In großen Apps empfohlen
Verwenden Sie eine Domainebene und Anwendungsfälle, wenn Sie Geschäftslogik wiederverwenden müssen, die mit der Datenschicht über mehrere ViewModels hinweg interagiert, oder wenn Sie die Komplexität der Geschäftslogik eines bestimmten ViewModel-Objekts vereinfachen möchten.

UI-Ebene

Die UI-Ebene dient dazu, die Anwendungsdaten auf dem Bildschirm anzuzeigen und als primärer Punkt für die Nutzerinteraktion zu dienen. Hier sind einige Best Practices für die UI-Ebene:

Empfehlung Beschreibung
Folgen Sie dem Unidirektionalen Datenfluss (UDF).
Dringend empfohlen
Folgen Sie den Prinzipien des Unidirektionalen Datenflusses (UDF), bei dem ViewModels den UI-Status mithilfe des Beobachtermusters ermitteln und Aktionen von der Benutzeroberfläche über Methodenaufrufe empfangen.
Verwenden Sie AAC ViewModels, wenn deren Vorteile für Ihre App gelten.
Dringend empfohlen
Verwenden Sie AAC ViewModels, um die Geschäftslogik zu verarbeiten, und rufen Sie Anwendungsdaten ab, um den UI-Status für die Benutzeroberfläche (Compose oder Android-Ansichten) anzuzeigen.

Weitere Best Practices für ViewModel

Vorteile von ViewModels

Lebenszyklusabhängige UI-Statuserfassung verwenden.
Dringend empfohlen
Erfassen Sie den Status der Benutzeroberfläche mit dem entsprechenden Builder für lebenszyklusfähige Koroutinen: repeatOnLifecycle im View-System und collectAsStateWithLifecycle in Jetpack Compose.

Weitere Informationen zu repeatOnLifecycle.

Weitere Informationen zu collectAsStateWithLifecycle.

Senden Sie keine Ereignisse von ViewModel an die UI.
Dringend empfohlen
Verarbeiten Sie das Ereignis sofort in ViewModel und führen Sie mit dem Ergebnis der Verarbeitung des Ereignisses eine Statusaktualisierung aus. Weitere Informationen zu UI-Ereignissen
Verwenden Sie eine App mit nur einer Aktivität.
Empfohlen
Wenn Ihre App mehr als einen Bildschirm hat, verwenden Sie Navigationsfragmente oder Navigationselemente verfassen, um zwischen Bildschirmen zu wechseln und einen Deeplink zu Ihrer App hinzuzufügen.
Verwenden Sie Jetpack Compose.
Empfohlen
Mit Jetpack Compose können Sie neue Apps für Smartphones, Tablets, faltbare Smartphones und Wear OS-Apps entwickeln.

Im folgenden Snippet wird beschrieben, wie Sie den Status der UI unter Berücksichtigung des Lebenszyklus erfassen:

Aufrufe

class MyFragment : Fragment() {

    private val viewModel: MyViewModel by viewModel()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        viewLifecycleOwner.lifecycleScope.launch {
            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.uiState.collect {
                    // Process item
                }
            }
        }
    }
}

Schreiben

@Composable
fun MyScreen(
    viewModel: MyViewModel = viewModel()
) {
    val uiState by viewModel.uiState.collectAsStateWithLifecycle()
}

ViewModel

ViewModels sind für die Bereitstellung des UI-Status und den Zugriff auf die Datenschicht verantwortlich. Hier sind einige Best Practices für ViewModels:

Empfehlung Beschreibung
ViewModels sollten unabhängig vom Android-Lebenszyklus sein.
Dringend empfohlen
ViewModels sollte keinen Verweis auf einen Lebenszyklus-bezogenen Typ enthalten. Übergeben Sie Activity, Fragment, Context oder Resources nicht als Abhängigkeit. Wenn ein Context in der ViewModel-Datei erforderlich ist, sollten Sie unbedingt prüfen, ob sich diese Ebene auf der richtigen Ebene befindet.
Verwenden Sie Koroutinen und Abläufe.
Dringend empfohlen

Das ViewModel interagiert mit den Daten- oder Domainebenen folgendermaßen:

  • Kotlin-Abläufe zum Empfangen von Anwendungsdaten
  • suspend-Funktionen, um Aktionen mit viewModelScope auszuführen.
ViewModels auf Bildschirmebene verwenden.
Dringend empfohlen

Verwenden Sie ViewModels nicht in wiederverwendbaren UI-Elementen. Sie sollten ViewModels in folgenden Ländern verwenden:

  • zusammensetzbare Funktionen auf Bildschirmebene
  • Aktivitäten/Fragmente in Ansichten,
  • Ziele oder Diagramme bei Verwendung von Jetpack Navigation
Verwenden Sie in wiederverwendbaren UI-Komponenten einfache Halterungsklassen.
Dringend empfohlen
Verwenden Sie einfache Zustandshalterklassen, um die Komplexität von wiederverwendbaren UI-Komponenten zu bewältigen. Dadurch kann der Zustand extern aufgezogen und gesteuert werden.
Verwenden Sie AndroidViewModel nicht.
Empfohlen
Verwenden Sie den Kurs ViewModel, nicht AndroidViewModel. Die Klasse Application sollte in ViewModel nicht verwendet werden. Verschieben Sie die Abhängigkeit stattdessen in die Benutzeroberfläche oder die Datenschicht.
Gibt einen UI-Status frei.
Empfohlen
ViewModels sollte Daten über eine einzelne Property namens uiState für die UI verfügbar machen. Wenn in der UI mehrere nicht zusammenhängende Daten angezeigt werden, kann die VM mehrere UI-Statusattribute zur Verfügung stellen.
  • Sie sollten uiState zu StateFlow machen.
  • Sie sollten die uiState mithilfe des Operators stateIn und der WhileSubscribed(5000)-Richtlinie (Beispiel) erstellen, wenn die Daten als Datenstream von anderen Hierarchieebenen kommen.
  • In einfacheren Fällen, in denen keine Datenstreams aus der Datenschicht kommen, kann ein MutableStateFlow als unveränderliches StateFlow verwendet werden (Beispiel).
  • Sie können die ${Screen}UiState als Datenklasse verwenden, die Daten, Fehler und Ladesignale enthalten kann. Diese Klasse könnte auch eine versiegelte Klasse sein, wenn die verschiedenen Zustände exklusiv sind.

Das folgende Snippet zeigt, wie der UI-Status einer ViewModel-Ressource angezeigt wird:

@HiltViewModel
class BookmarksViewModel @Inject constructor(
    newsRepository: NewsRepository
) : ViewModel() {

    val feedState: StateFlow<NewsFeedUiState> =
        newsRepository
            .getNewsResourcesStream()
            .mapToFeedState(savedNewsResourcesState)
            .stateIn(
                scope = viewModelScope,
                started = SharingStarted.WhileSubscribed(5_000),
                initialValue = NewsFeedUiState.Loading
            )

    // ...
}

Lebenszyklus

Im Folgenden finden Sie einige Best Practices für die Arbeit mit dem Android-Lebenszyklus:

Empfehlung Beschreibung
Überschreiben Sie keine Lebenszyklusmethoden in „Aktivitäten“ oder „Fragmente“.
Dringend empfohlen
Überschreiben Sie keine Lebenszyklusmethoden wie onResume in „Aktivitäten“ oder „Fragmente“. Verwenden Sie stattdessen LifecycleObserver. Wenn die Anwendung Aufgaben ausführen muss, wenn der Lebenszyklus einen bestimmten Lifecycle.State erreicht, verwenden Sie die repeatOnLifecycle API.

Das folgende Snippet beschreibt, wie Vorgänge bei einem bestimmten Lebenszyklusstatus ausgeführt werden:

Aufrufe

class MyFragment: Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver {
            override fun onResume(owner: LifecycleOwner) {
                // ...
            }
            override fun onPause(owner: LifecycleOwner) {
                // ...
            }
        }
    }
}

Schreiben

@Composable
fun MyApp() {

    val lifecycleOwner = LocalLifecycleOwner.current
    DisposableEffect(lifecycleOwner, ...) {
        val lifecycleObserver = object : DefaultLifecycleObserver {
            override fun onStop(owner: LifecycleOwner) {
                // ...
            }
        }

        lifecycleOwner.lifecycle.addObserver(lifecycleObserver)
        onDispose {
            lifecycleOwner.lifecycle.removeObserver(lifecycleObserver)
        }
    }
}

Abhängigkeiten verarbeiten

Es gibt mehrere Best Practices, die Sie beim Verwalten von Abhängigkeiten zwischen Komponenten beachten sollten:

Empfehlung Beschreibung
Verwenden Sie die Abhängigkeitsinjektion.
Dringend empfohlen
Wenden Sie die Best Practices für die Abhängigkeitsinjektion an, vor allem die Konstruktor-Injektion.
Wechseln Sie bei Bedarf zu einer Komponente.
Dringend empfohlen
Wechseln Sie zu einem Abhängigkeitscontainer, wenn der Typ änderbare Daten enthält, die freigegeben werden müssen, oder wenn die Initialisierung des Typs teuer ist und in der App weit verbreitet ist.
Verwenden Sie den Hilt.
Empfohlen
Verwenden Sie in einfachen Apps Hilt oder manuelle Abhängigkeitsinjektion. Verwenden Sie Hilt, wenn Ihr Projekt komplex genug ist. Beispiel:
  • Integration mehrerer Bildschirme mit ViewModels
  • Nutzung von WorkManager – Integration
  • Erweiterte Nutzung der Navigation, wie z. B. ViewModels, die auf die Navigationsgrafik beschränkt sind.

Testen

Im Folgenden finden Sie einige Best Practices für Tests:

Empfehlung Beschreibung
Wissen, was getestet werden muss:
Dringend empfohlen

Wenn das Projekt nicht grob so einfach ist wie eine Hello World-Anwendung, sollten Sie es zumindest mit folgenden Elementen testen:

  • Unittest von ViewModels, einschließlich Abläufen.
  • Unittest-Datenschichtentitäten also Repositories und Datenquellen.
  • UI-Navigationstests, die als Regressionstests in CI nützlich sind
Fälschungen gegenüber Simulationen bevorzugen.
Dringend empfohlen
Weitere Informationen finden Sie in der Dokumentation zur Verwendung von Test-Doubles in der Android-Dokumentation.
StateFlows testen.
Dringend empfohlen
Beim Testen von StateFlow:

Weitere Informationen finden Sie im Leitfaden zu Tests im Android-DAC.

Fotomodelle

Halten Sie sich bei der Entwicklung von Modellen in Ihren Apps an die folgenden Best Practices:

Empfehlung Beschreibung
Erstellen Sie in komplexen Apps ein Modell pro Ebene.
Empfohlen

Erstellen Sie in komplexen Apps neue Modelle in verschiedenen Ebenen oder Komponenten, wenn es sinnvoll ist. Betrachten Sie die folgenden Beispiele:

  • Eine Remote-Datenquelle kann das Modell, das sie über das Netzwerk erhält, einer einfacheren Klasse zuordnen, die genau die Daten enthält, die die App benötigt
  • Repositories können DAO-Modelle einfacheren Datenklassen mit genau den Informationen zuordnen, die die UI-Ebene benötigt.
  • ViewModel kann Datenschichtmodelle in UiState-Klassen enthalten.

Namenskonventionen

Beachten Sie beim Benennen Ihrer Codebasis die folgenden Best Practices:

Empfehlung Beschreibung
Benennungsmethoden
Optional
Methoden sollten eine Verb-Phrase sein. z. B. makePayment().
Eigenschaften benennen
Optional
Eigenschaften sollten eine Nominalphrase sein. z. B. inProgressTopicSelection.
Datenstreams benennen
Optional
Wenn eine Klasse einen Flow-Stream, LiveData oder einen anderen Stream verfügbar macht, lautet die Namenskonvention get{model}Stream(). Beispiel: getAuthorStream(): Flow<Author>. Wenn die Funktion eine Liste von Modellen zurückgibt, sollte der Modellname in der Pluralform vorliegen: getAuthorsStream(): Flow<List<Author>>
Schnittstellenimplementierungen benennen
Optional
Namen für die Implementierungen von Schnittstellen sollten aussagekräftig sein. Verwenden Sie Default als Präfix, wenn kein besserer Name gefunden werden kann. Für eine NewsRepository-Schnittstelle könnten Sie beispielsweise OfflineFirstNewsRepository oder InMemoryNewsRepository verwenden. Wenn Sie keinen guten Namen finden, verwenden Sie DefaultNewsRepository. Gefälschte Implementierungen müssen das Präfix Fake haben, wie in FakeAuthorsRepository.