Empfehlungen für die Android-Architektur

Auf dieser Seite werden mehrere Best Practices und Empfehlungen zur Architektur vorgestellt. Wenn Sie sie übernehmen, können Sie die Qualität, Robustheit und Skalierbarkeit Ihrer App verbessern. Außerdem erleichtern sie die Wartung und das Testen Ihrer App.

Die Best Practices unten sind nach Themen gruppiert. Jede hat eine Priorität, die angibt, wie stark die Empfehlung ist. Die Liste der Prioritäten sieht so aus:

  • Dringend empfohlen:Implementieren Sie diese Best Practice, es sei denn, sie widerspricht Ihrem Ansatz grundlegend.
  • Empfohlen:Diese Best Practice wird Ihre App wahrscheinlich verbessern.
  • Optional:Diese Best Practice kann Ihre App unter bestimmten Umständen verbessern.

Mehrschichtige Architektur

Unsere empfohlene mehrschichtige Architektur bevorzugt die Trennung von Zuständigkeiten. Sie leitet die Benutzeroberfläche von Datenmodellen ab, entspricht dem Prinzip der Single Source of Truth, und folgt den Prinzipien des unidirektionalen Datenflusses. Hier sind einige Best Practices für die mehrschichtige Architektur:

Empfehlung Beschreibung
Verwenden Sie eine klar definierte Datenschicht. Die Datenschicht stellt Anwendungsdaten für den Rest der App bereit und enthält den Großteil der Geschäftslogik Ihrer App.
  • Erstellen Sie Repositories, auch wenn sie nur eine einzige Datenquelle enthalten.
  • In kleinen Apps können Sie die Typen der Datenschicht in einem data-Paket oder -Modul platzieren.
Verwenden Sie eine klar definierte UI-Schicht. Die UI-Schicht zeigt die Anwendungsdaten auf dem Bildschirm an und dient als primärer Punkt der Nutzerinteraktion. Jetpack Compose ist das empfohlene moderne Toolkit für die Erstellung der Benutzeroberfläche Ihrer App.
  • In kleinen Apps können Sie die Typen der Datenschicht in einem ui-Paket oder -Modul platzieren.
Weitere Informationen zu Best Practices für die UI-Schicht finden Sie unter UI-Schicht.
Stellen Sie Anwendungsdaten aus der Datenschicht über ein Repository bereit.

Achten Sie darauf, dass Komponenten in der UI-Schicht wie zusammensetzbare Funktionen oder ViewModels 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 Flows. Verwenden Sie Coroutinen und Flows, um zwischen Schichten zu kommunizieren.

Weitere Informationen zu Best Practices für Coroutinen finden Sie unter Best Practices für Coroutinen in Android.

Verwenden Sie eine Domain-Schicht. Verwenden Sie eine Domain-Schicht mit Anwendungsfällen, wenn Sie Geschäftslogik wiederverwenden müssen, die mit der Datenschicht über mehrere ViewModels interagiert, oder wenn Sie die Komplexität der Geschäftslogik eines bestimmten ViewModels vereinfachen möchten.

UI-Layer

Die Rolle der UI-Schicht ist es, die Anwendungsdaten auf dem Bildschirm anzuzeigen und als primärer Punkt der Nutzerinteraktion zu dienen. Hier sind einige Best Practices für die UI-Schicht:

Empfehlung Beschreibung
Folgen Sie dem unidirektionalen Datenfluss (Unidirectional Data Flow, UDF). Folgen Sie den Prinzipien des unidirektionalen Datenflusses (Unidirectional Data Flow, UDF). Dabei stellen ViewModels den UI-Status mithilfe des Beobachtermusters bereit und empfangen Aktionen von der Benutzeroberfläche über Methodenaufrufe.
Verwenden Sie AAC-ViewModels, wenn ihre Vorteile für Ihre App gelten. Verwenden Sie AAC-ViewModels, um Geschäftslogik zu verarbeiten und Anwendungsdaten abzurufen, um den UI-Status für die Benutzeroberfläche bereitzustellen.

Weitere Informationen zu Best Practices für ViewModels finden Sie unter Architektur-Empfehlungen.

Weitere Informationen zu den Vorteilen von ViewModels finden Sie unter Das ViewModel als Status-Holder für die Geschäftslogik.

Verwenden Sie die lebenszyklusbezogene Erfassung des UI-Status. Erfassen Sie den UI-Status über die Benutzeroberfläche mit dem entsprechenden lebenszyklusbezogenen Coroutinen-Builder, collectAsStateWithLifecycle.

Weitere Informationen zu collectAsStateWithLifecycle.

Senden Sie keine Ereignisse vom ViewModel an die Benutzeroberfläche. Verarbeiten Sie das Ereignis sofort im ViewModel und veranlassen Sie eine Statusaktualisierung mit dem Ergebnis der Ereignisverarbeitung. Weitere Informationen zu UI-Ereignissen finden Sie unter ViewModel-Ereignisse verarbeiten.
Verwenden Sie eine Single-Activity-Anwendung. Verwenden Sie Navigation 3, um zwischen Bildschirmen zu wechseln und Deep-Links zu Ihrer App zu erstellen, wenn Ihre App mehr als einen Bildschirm hat.
Verwenden Sie Jetpack Compose. Verwenden Sie Jetpack Compose, um neue Apps für Smartphones, Tablets, faltbare Geräte und Wear OS zu erstellen.

Das folgende Snippet zeigt, wie Sie den UI-Status lebenszyklusbezogen erfassen:

  @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 zuständig. Hier sind einige Best Practices für ViewModels:

Empfehlung Beschreibung
Halten Sie ViewModels unabhängig vom Android-Lebenszyklus. Halten Sie in ViewModels keine Referenz auf einen lebenszyklusbezogenen Typ. Übergeben Sie Activity, Context oder Resources nicht als Abhängigkeit. Wenn etwas im ViewModel einen Context benötigt, prüfen Sie sorgfältig, ob es sich in der richtigen Schicht befindet.
Verwenden Sie Coroutinen und Flows.

Das ViewModel interagiert mit der Daten- oder Domain-Schicht mit Folgendem:

  • Kotlin-Flows zum Empfangen von Anwendungsdaten
  • suspend -Funktionen zum Ausführen von Aktionen mit viewModelScope
Verwenden Sie ViewModels auf Bildschirmebene.

Verwenden Sie keine ViewModels in wiederverwendbaren UI-Elementen. Sie sollten ViewModels in folgenden Fällen verwenden:

  • Zusammensetzbare Funktionen auf Bildschirmebene
  • Ziele oder Graphen bei Verwendung von Jetpack Navigation

Verwenden Sie für komplexere zusammensetzbare Funktionen oder solche mit dynamischem Verhalten basierend auf dem Status rememberViewModelStoreOwner(), um ein ViewModel direkt auf den Aufrufort der zusammensetzbaren Funktion zu beschränken.

Verwenden Sie einfache Status-Holder-Klassen in wiederverwendbaren UI-Komponenten. Verwenden Sie einfache Status-Holder-Klassen, um die Komplexität in wiederverwendbaren UI-Komponenten zu verarbeiten. Dadurch kann der Status ausgelagert und extern gesteuert werden.
Verwenden Sie nicht AndroidViewModel. Verwenden Sie die ViewModel Klasse und nicht AndroidViewModel. Verwenden Sie die Klasse Application nicht im ViewModel. Verschieben Sie die Abhängigkeit stattdessen in die UI- oder Datenschicht.
Stellen Sie einen UI-Status bereit. Sorgen Sie dafür, dass Ihre ViewModels Daten über eine einzelne Property namens uiState für die Benutzeroberfläche bereitstellen. Wenn die Benutzeroberfläche mehrere unabhängige Daten enthält, kann das ViewModel mehrere UI-Status-Properties bereitstellen.
  • Machen Sie uiState zu einem StateFlow.
  • Erstellen Sie uiState mit dem stateIn-Operator und der WhileSubscribed(5000)-Richtlinie, wenn die Daten als Datenstream aus anderen Schichten der Hierarchie stammen. Siehe dieses Codebeispiel.
  • In einfacheren Fällen ohne Datenstreams aus der Datenschicht ist es akzeptabel, ein MutableStateFlow zu verwenden, das als unveränderliches StateFlow bereitgestellt wird.
  • Sie können ${Screen}UiState als Datenklasse verwenden, die Daten, Fehler und Ladesignale enthalten kann. Diese Klasse kann auch eine versiegelte Klasse sein, wenn sich die verschiedenen Status gegenseitig ausschließen.

Das folgende Snippet zeigt, wie Sie den UI-Status aus einem ViewModel bereitstellen:

@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

Beachten Sie die Best Practices für die Arbeit mit dem Lebenszyklus von Aktivitäten :

Empfehlung Beschreibung
Verwenden Sie lebenszyklusbezogene Effekte in zusammensetzbaren Funktionen, anstatt die Lebenszyklus-Callbacks von Activity zu überschreiben.

Überschreiben Sie keine Lebenszyklusmethoden von Activity wie onResume, um UI-bezogene Aufgaben auszuführen. Verwenden Sie stattdessen die LifecycleEffects von Compose oder lebenszyklusbezogene Coroutinen-Bereiche:

  • Verwenden Sie LifecycleStartEffect, um synchrone Aufgaben auszuführen, wenn Ihre Aktivität gestartet und beendet wird.
  • Verwenden Sie LifecycleResumeEffect, um synchrone Aufgaben auszuführen, wenn Ihre Aktivität fortgesetzt und pausiert wird.
  • Verwenden Sie repeatOnLifecycle, um asynchrone Aufgaben als Reaktion auf Lebenszyklusereignisse auszuführen.
  • Erfassen Sie asynchrone Daten aus Flows mit collectAsStateWithLifecycle.

Das folgende Snippet zeigt, wie Sie Vorgänge in einem bestimmten Lebenszyklusstatus ausführen:

  @Composable
  fun LocationChangedEffect(
    locationManager: LocationManager,
    onLocationChanged: (Location) -> Unit
  ) {
    val currentOnLocationChanged by rememberUpdatedState(onLocationChanged)

    LifecycleStartEffect(locationManager) {
        val listener = LocationListener { newLocation ->
            currentOnLocationChanged(newLocation)
        }

        try {
            locationManager.requestLocationUpdates(
                LocationManager.GPS_PROVIDER,
                1000L,
                1f,
                listener,
            )
        } catch (e: SecurityException) {
            // TODO: Handle missing permissions
        }

        onStopOrDispose {
            locationManager.removeUpdates(listener)
        }
    }
  }

Abhängigkeiten verarbeiten

Beachten Sie die Best Practices beim Verwalten von Abhängigkeiten zwischen Komponenten:

Empfehlung Beschreibung
Verwenden Sie die Abhängigkeitsinjektion. Beachten Sie die Best Practices für die Abhängigkeitsinjektion, vor allem die Konstruktorinjektion, wenn möglich.
Beschränken Sie den Bereich bei Bedarf auf eine Komponente. Beschränken Sie den Bereich auf einen Abhängigkeitscontainer, wenn der Typ veränderliche Daten enthält, die freigegeben werden müssen, oder wenn der Typ teuer zu initialisieren ist und häufig in der App 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, z. B. wenn es Folgendes enthält:
  • Mehrere Bildschirme mit ViewModels
  • Verwendung von WorkManager
  • ViewModels, die auf den Back-Stack der Navigation beschränkt sind

Test

Hier sind einige Best Practices für Tests:

Empfehlung Beschreibung
Wissen, was getestet werden muss.

Sofern das Projekt nicht so einfach wie eine „Hello World“-App ist, testen Sie es. Es sollte mindestens Folgendes enthalten:

  • Unittests für ViewModels, einschließlich Flows
  • Unittests für Entitäten der Datenschicht, d. h. Repositories und Datenquellen
  • UI-Navigationstests, die als Regressionstests in der CI-Pipeline nützlich sind
Bevorzugen Sie Fakes gegenüber Mocks. Weitere Informationen zur Verwendung von Fakes finden Sie unter Test Doubles in Android verwenden.
Testen Sie StateFlows. Gehen Sie beim Testen von StateFlow so vor:

Weitere Informationen finden Sie unter Was sollte in Android getestet werden? und Compose-Layout testen.

Modelle

Beachten Sie diese Best Practices, wenn Sie Modelle in Ihren Apps entwickeln:

Empfehlung Beschreibung
Erstellen Sie in komplexen Apps ein Modell pro Schicht.

Erstellen Sie in komplexen Apps neue Modelle in verschiedenen Schichten 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 zuordnen, die nur die Daten enthält, die die App benötigt.
  • Repositories können DAO-Modelle einfacheren Datenklassen zuordnen, die nur die Informationen enthalten, die die UI-Schicht benötigt.
  • ViewModel kann Modelle der Datenschicht in UiState-Klassen enthalten.

Namenskonventionen

Beachten Sie beim Benennen Ihrer Codebasis die folgenden Best Practices:

Empfehlung Beschreibung
Methoden benennen.
Optional
Verwenden Sie Verbgruppen, um Methoden zu benennen, z. B. makePayment().
Properties benennen.
Optional
Verwenden Sie Nomengruppen, um Properties zu benennen, z. B. inProgressTopicSelection.
Datenstreams benennen.
Optional
Wenn eine Klasse einen Flow-Stream oder einen anderen Stream bereitstellt, lautet die Namenskonvention get{model}Stream, z. B. getAuthorStream(): Flow<Author>. Wenn die Funktion eine Liste von Modellen zurückgibt, verwenden Sie den Plural des Modellnamens: getAuthorsStream(): Flow<List<Author>>.
Implementierungen von Schnittstellen benennen.
Optional
Verwenden Sie aussagekräftige Namen für die Implementierungen von Schnittstellen. Verwenden Sie Default als Präfix, wenn kein besserer Name gefunden werden kann. Für eine NewsRepository-Schnittstelle können Sie beispielsweise OfflineFirstNewsRepository oder InMemoryNewsRepository verwenden. Wenn Sie keinen guten Namen finden, verwenden Sie DefaultNewsRepository. Stellen Sie dem Namen von Fake-Implementierungen das Präfix Fake voran, z. B. FakeAuthorsRepository.

Zusätzliche Ressourcen

Weitere Informationen zur Android-Architektur finden Sie in den folgenden zusätzlichen Ressourcen:

Dokumentation

Inhalte ansehen