Empfehlungen für die Android-Architektur

Auf dieser Seite finden Sie mehrere Best Practices und Empfehlungen für die Architektur. Sie können sie verwenden, um die Qualität, Robustheit und Skalierbarkeit Ihrer App zu verbessern. Außerdem erleichtern sie die Wartung und das Testen Ihrer App.

Die folgenden Best Practices sind nach Thema gruppiert. Jede hat eine Priorität, die widerspiegelt, wie sehr das Team sie empfiehlt. Die Prioritätenliste sieht so aus:

  • Dringend empfohlen:Sie sollten diese Praxis implementieren, es sei denn, sie steht grundsätzlich im Widerspruch zu Ihrem Ansatz.
  • Empfohlen:Dadurch lässt sich Ihre App wahrscheinlich verbessern.
  • Optional:Diese Vorgehensweise kann Ihre App unter bestimmten Umständen verbessern.

Mehrschichtige Architektur

Unsere empfohlene mehrschichtige Architektur fördert die Aufgabentrennung. Die Benutzeroberfläche basiert auf Datenmodellen, entspricht dem Prinzip der Single Source of Truth und folgt den Prinzipien des einseitigen Datenflusses. Im Folgenden finden Sie einige Best Practices für die mehrschichtige Architektur:

Empfehlung Beschreibung
Verwenden Sie eine klar definierte Datenschicht. Die Datenebene 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 Datenebenentypen in einem data-Paket oder ‑Modul platzieren.
Verwenden Sie eine klar definierte UI-Ebene. Die UI-Ebene zeigt die Anwendungsdaten auf dem Bildschirm an und dient als primärer Interaktionspunkt für Nutzer.
  • In kleinen Apps können Sie Datenebenentypen in einem ui-Paket oder ‑Modul platzieren.
Weitere Best Practices für die UI-Ebene finden Sie hier.
Die Datenschicht sollte Anwendungsdaten über ein Repository bereitstellen.

Komponenten in der UI-Ebene wie Composeables, 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 des Status der Netzwerkverbindung.
Verwenden Sie Coroutinen und Abläufe. Verwenden Sie Koroutinen und Abläufe, um zwischen Ebenen zu kommunizieren.

Weitere Best Practices für Tasks

Verwenden Sie eine Domainebene. Verwenden Sie eine Domainebene, wenn Sie Geschäftslogik, die mit der Datenebene in mehreren ViewModels interagiert, wiederverwenden möchten oder die Komplexität der Geschäftslogik eines bestimmten ViewModels vereinfachen möchten.

UI-Ebene

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

Empfehlung Beschreibung
Folgen Sie dem einseitigen Datenfluss (Unidirectional Data Flow, UDF). Beachten Sie die Prinzipien des einseitigen Datenflusses (Unidirectional Data Flow, UDF), bei denen ViewModels den UI-Status mithilfe des Beobachtermusters freigeben und Aktionen von der UI über Methodenaufrufe empfangen.
Verwenden Sie AAC-ViewModels, wenn die Vorteile für Ihre App gelten. Verwenden Sie AAC-ViewModels, um Geschäftslogik zu verarbeiten, und rufen Sie Anwendungsdaten ab, um den UI-Status für die UI (Compose- oder Android-Views) freizugeben.

Weitere Best Practices für ViewModel

Weitere Informationen zu den Vorteilen von ViewModels

Verwenden Sie die lebenszyklusorientierte Erfassung des UI-Status. Erfassen Sie den UI-Status mit dem entsprechenden lebenszyklusbewussten Coroutinen-Builder: 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 Benutzeroberfläche. Das Ereignis wird sofort im ViewModel verarbeitet und es wird ein Statusupdate mit dem Ergebnis der Ereignisbearbeitung ausgelöst. Weitere Informationen zu UI-Ereignissen
Verwenden Sie eine App mit einer einzelnen Aktivität. Verwenden Sie Navigationsfragmente oder Navigation Compose, um zwischen Bildschirmen zu wechseln und einen Deeplink zu Ihrer App herzustellen, wenn Ihre App mehr als einen Bildschirm hat.
Verwenden Sie Jetpack Compose. Mit Jetpack Compose können Sie neue Apps für Smartphones, Tablets, faltbare Geräte und Wear OS entwickeln.

Im folgenden Snippet wird beschrieben, wie der UI-Status laufzeitabhängig erfasst wird:

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 des Zugriffs auf die Datenschicht zuständig. Im Folgenden finden Sie einige Best Practices für ViewModels:

Empfehlung Beschreibung
ViewModels sollten unabhängig vom Android-Lebenszyklus sein. ViewModels dürfen keine Referenz auf einen lebenszyklusbezogenen Typ enthalten. Übergeben Sie Activity, Fragment, Context oder Resources nicht als Abhängigkeit. Wenn für etwas im ViewModel eine Context erforderlich ist, sollten Sie genau prüfen, ob sich das in der richtigen Schicht befindet.
Verwenden Sie Coroutinen und Abläufe.

Das ViewModel interagiert mit den Daten- oder Domainebenen über:

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

Verwenden Sie ViewModels nicht in wiederverwendbaren Elementen der Benutzeroberfläche. ViewModels sollten in folgenden Fällen verwendet werden:

  • Composeable-Elemente auf Bildschirmebene,
  • Aktivitäten/Fragmente in Aufrufen,
  • Ziele oder Grafiken bei Verwendung von Jetpack Navigation
Verwenden Sie einfache Statushalterklassen in wiederverwendbaren UI-Komponenten. Verwenden Sie einfache State-Holder-Klassen, um die Komplexität wiederverwendbarer UI-Komponenten zu bewältigen. Auf diese Weise kann der Zustand extern hochgezogen und gesteuert werden.
Verwenden Sie nicht AndroidViewModel. Verwenden Sie die Klasse ViewModel, nicht AndroidViewModel. Die Klasse Application sollte nicht im ViewModel verwendet werden. Verschieben Sie die Abhängigkeit stattdessen in die Benutzeroberfläche oder die Datenebene.
Zeigt einen UI-Status an. ViewModels sollte Daten über eine einzige Property namens uiState für die Benutzeroberfläche bereitstellen. Wenn die Benutzeroberfläche mehrere, nicht zusammenhängende Daten enthält, kann die VM mehrere UI-Statuseigenschaften freigeben.
  • Sie sollten uiState in StateFlow ändern.
  • Sie sollten die uiState mit dem Operator stateIn und der WhileSubscribed(5000)-Richtlinie (Beispiel) erstellen, wenn die Daten als Stream von Daten aus anderen Ebenen der Hierarchie kommen.
  • Bei einfacheren Fällen ohne Datenstreams aus der Datenschicht ist es zulässig, eine MutableStateFlow als unveränderliche StateFlow zu verwenden (Beispiel).
  • Sie können die ${Screen}UiState als Datenklasse festlegen, die Daten, Fehler und Ladesignale enthalten kann. Diese Klasse kann auch eine versiegelte Klasse sein, wenn die verschiedenen Status sich gegenseitig ausschließen.

Das folgende Snippet veranschaulicht, wie der UI-Status aus einer ViewModel-Ressource verfügbar gemacht 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 die Lebenszyklusmethoden in Aktivitäten oder Fragmenten nicht. Überschreiben Sie Lebenszyklusmethoden wie onResume in Aktivitäten oder Fragmenten nicht. Verwenden Sie stattdessen LifecycleObserver. Wenn die Anwendung Arbeiten ausführen muss, wenn der Lebenszyklus einen bestimmten Lifecycle.State erreicht, verwenden Sie die repeatOnLifecycle API.

Im folgenden Snippet wird beschrieben, 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 behandeln

Es gibt mehrere Best Practices, die Sie bei der Verwaltung von Abhängigkeiten zwischen Komponenten beachten sollten:

Empfehlung Beschreibung
Verwenden Sie die Abhängigkeitsinjektion. Verwenden Sie die Best Practices für die Dependency Injection, vor allem die Konstruktor-Injection, wenn möglich.
Wechseln Sie bei Bedarf zu einer Komponente. Beschränken Sie den Gültigkeitsbereich auf einen Abhängigkeitscontainer, wenn der Typ veränderliche Daten enthält, die freigegeben werden müssen, oder wenn die Initialisierung des Typs aufwendig ist und er in der App häufig verwendet wird.
Verwenden Sie Hilt. Verwenden Sie Hilt oder die manuelle Abhängigkeitsinjektion in einfachen Apps. Verwenden Sie Hilt, wenn Ihr Projekt komplex genug ist. Zum Beispiel:
  • Mehrere Bildschirme mit ViewModels-Integration
  • WorkManager-Nutzung – Integration
  • Erweiterte Navigationsnutzung, z. B. ViewModels, die auf den Navigationsgraphen beschränkt sind – Integration

Testen

Hier einige Best Practices für Tests:

Empfehlung Beschreibung
Wissen, was getestet werden soll

Sofern das Projekt nicht ungefähr so einfach wie eine „Hello World“-Anwendung ist, sollten Sie es mindestens mit folgenden Elementen testen:

  • ViewModels, einschließlich Abläufe, mithilfe von Unit-Tests testen
  • Entitäten der Datenebene für den Unit-Test Das sind Repositories und Datenquellen.
  • UI-Navigationstests, die sich als Regressionstests in der CI eignen.
Verwenden Sie lieber Fakes als Mockups. Weitere Informationen finden Sie in der Android-Dokumentation zum Verwenden von Test-Doubles.
Testen Sie StateFlows. Beim Testen von StateFlow:

Weitere Informationen finden Sie im Leitfaden Was im Android-DAC-Test geprüft werden sollte.

Modelle

Beachten Sie beim Entwickeln von Modellen in Ihren Apps die folgenden Best Practices:

Empfehlung Beschreibung
Ein Modell pro Ebene in komplexen Apps erstellen

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

  • Eine Remote-Datenquelle kann das Modell, das sie über das Netzwerk empfängt, einer einfacheren Klasse mit nur den Daten zuordnen, die die App benötigt.
  • Repositories können DAO-Modelle mit einfacheren Datenklassen mit nur den Informationen abgleichen, die für die UI-Ebene erforderlich sind.
  • Das ViewModel kann Datenebenenmodelle in UiState-Klassen enthalten.

Namenskonventionen

Beachten Sie beim Benennen Ihrer Codebasis die folgenden Best Practices:

Empfehlung Beschreibung
Benennungsmethoden
Optional
Methoden sollten Verben sein. Beispiel: makePayment().
Namenskonventionen für Properties
Optional
Unterkünfte sollten als Substantivphrase angegeben werden. Beispiel: 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 im Plural stehen: getAuthorsStream(): Flow<List<Author>>
Namenskonventionen für die Implementierung von Schnittstellen
Optional
Die 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, z. B. FakeAuthorsRepository.