Zalecenia dotyczące architektury Androida

Na tej stronie znajdziesz kilka sprawdzonych metod i zaleceń dotyczących architektury. Wprowadź je, aby poprawić jakość, niezawodność i skalowalność aplikacji. Ułatwiają też utrzymywanie i testowanie aplikacji.

Sprawdzone metody poniżej są pogrupowane według tematu. Każda z nich ma priorytet, który odzwierciedla siłę rekomendacji. Lista priorytetów jest następująca:

  • Zdecydowanie zalecane: wdróż tę praktykę, chyba że jest ona sprzeczna z Twoim podejściem.
  • Zalecane: ta sprawdzona metoda prawdopodobnie poprawi jakość Twojej aplikacji.
  • Opcjonalnie: w określonych okolicznościach może to poprawić jakość aplikacji.

Architektura warstwowa

Zalecana przez nas architektura warstwowa sprzyja rozdzieleniu zadań. Wyświetla interfejs na podstawie modeli danych, jest zgodny z zasadą jednego źródła danych i przestrzega zasad jednokierunkowego przepływu danych. Oto kilka sprawdzonych metod dotyczących architektury warstwowej:

Rekomendacja Opis
Używaj jasno określonej warstwy danych. Warstwa danych udostępnia dane aplikacji pozostałym jej częściom i zawiera większość logiki biznesowej aplikacji.
  • Twórz repozytoria, nawet jeśli zawierają tylko jedno źródło danych.
  • W przypadku małych aplikacji możesz umieścić typy warstwy danych w datapakiecie lub module.
Używaj wyraźnie zdefiniowanej warstwy interfejsu. Warstwa interfejsu wyświetla dane aplikacji na ekranie i służy jako główny punkt interakcji użytkownika. Jetpack Compose to zalecany nowoczesny zestaw narzędzi do tworzenia interfejsu aplikacji.
  • W przypadku małych aplikacji możesz umieścić typy warstwy danych w uipakiecie lub module.
Więcej informacji o sprawdzonych metodach dotyczących warstwy interfejsu znajdziesz w artykule Warstwa interfejsu.
Udostępnij dane aplikacji z warstwy danych za pomocą repozytorium.

Upewnij się, że komponenty w warstwie interfejsu, takie jak funkcje kompozycyjne czy modele ViewModel, nie wchodzą w bezpośrednią interakcję ze źródłem danych. Przykłady źródeł danych:

  • Bazy danych, DataStore, SharedPreferences, interfejsy API Firebase.
  • dostawców lokalizacji GPS;
  • dostawców danych Bluetooth;
  • dostawców stanu połączenia sieciowego;
Używaj współprogramów i przepływów. Do komunikacji między warstwami używaj korutyn i przepływów.

Więcej informacji o sprawdzonych metodach dotyczących współprogramów znajdziesz w artykule Sprawdzone metody dotyczące współprogramów na Androidzie.

Użyj warstwy domeny. Używaj warstwy domeny z przypadkami użycia, jeśli chcesz ponownie wykorzystać logikę biznesową, która wchodzi w interakcję z warstwą danych w wielu modelach ViewModel, lub uprościć złożoność logiki biznesowej konkretnego modelu ViewModel.

Warstwa interfejsu

Warstwa interfejsu służy do wyświetlania danych aplikacji na ekranie i jest głównym punktem interakcji użytkownika. Oto kilka sprawdzonych metod dotyczących warstwy interfejsu:

Rekomendacja Opis
Postępuj zgodnie z zasadami jednokierunkowego przepływu danych (UDF). Postępuj zgodnie z zasadami jednokierunkowego przepływu danych (UDF), w których klasy ViewModel udostępniają stan interfejsu za pomocą wzorca obserwatora i otrzymują działania z interfejsu za pomocą wywołań metod.
Używaj AAC ViewModels, jeśli ich zalety mają zastosowanie w Twojej aplikacji. Używaj AAC ViewModels do obsługi logiki biznesowej i pobierania danych aplikacji, aby udostępniać interfejsowi stan interfejsu.

Więcej informacji o sprawdzonych metodach dotyczących ViewModel znajdziesz w artykule Zalecenia dotyczące architektury.

Więcej informacji o korzyściach klasy ViewModel znajdziesz w artykule ViewModel jako zmienna stanu logiki biznesowej.

Korzystaj ze zbierania stanu interfejsu, które uwzględnia cykl życia. Zbierz stan interfejsu z interfejsu za pomocą odpowiedniego konstruktora współprogramu uwzględniającego cykl życia, collectAsStateWithLifecycle.

Więcej informacji o  collectAsStateWithLifecycle.

Nie wysyłaj zdarzeń z ViewModel do interfejsu. Natychmiast przetwórz zdarzenie w obiekcie ViewModel i wywołaj aktualizację stanu z wynikiem obsługi zdarzenia. Więcej informacji o zdarzeniach interfejsu znajdziesz w artykule Obsługa zdarzeń ViewModel.
Używaj aplikacji z jedną aktywnością. Użyj Navigation 3, aby poruszać się między ekranami i precyzyjnie linkować do aplikacji, jeśli ma ona więcej niż 1 ekran.
Używaj Jetpack Compose. Używaj Jetpack Compose do tworzenia nowych aplikacji na telefony, tablety, urządzenia składane i zegarki z Wear OS.

Poniższy fragment kodu pokazuje, jak zbierać stan interfejsu w sposób uwzględniający cykl życia:

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

ViewModel

ViewModele odpowiadają za udostępnianie stanu interfejsu i dostęp do warstwy danych. Oto kilka sprawdzonych metod dotyczących klasy ViewModel:

Rekomendacja Opis
Zachowaj niezależność obiektów ViewModel od cyklu życia Androida. W klasach ViewModel nie przechowuj odwołań do żadnych typów związanych z cyklem życia. Nie przekazuj jako zależności wartości Activity, Context ani Resources. Jeśli coś wymaga Context w ViewModelu, dokładnie sprawdź, czy jest to odpowiednia warstwa.
Używaj współprogramów i przepływów.

ViewModel wchodzi w interakcję z warstwami danych lub domeny za pomocą tych elementów:

  • Kotlin Flow do odbierania danych aplikacji
  • funkcje suspend do wykonywania działań za pomocą viewModelScope,
Używaj obiektów ViewModel na poziomie ekranu.

Nie używaj klas ViewModel w elementach interfejsu, które można ponownie wykorzystać. Modelu widoku należy używać w tych przypadkach:

  • komponenty na poziomie ekranu,
  • aktywności lub fragmenty w widokach,
  • miejsc docelowych lub wykresów podczas korzystania z nawigacji Jetpack.
Używaj zwykłych klas przechowujących stan w komponentach interfejsu wielokrotnego użytku. Używaj zwykłych klas przechowujących stan do obsługi złożoności w komponentach interfejsu wielokrotnego użytku. W takim przypadku stan może być przeniesiony i sterowany zewnętrznie.
Nie używaj AndroidViewModel. Użyj klasy ViewModel, a nie AndroidViewModel. Nie używaj klasy Application w obiekcie ViewModel. Zamiast tego przenieś zależność do interfejsu lub warstwy danych.
Udostępnij stan interfejsu. Spraw, aby obiekty ViewModel udostępniały dane interfejsowi za pomocą jednej właściwości o nazwie uiState. Jeśli interfejs wyświetla wiele niepowiązanych ze sobą danych, maszyna wirtualna może udostępniać wiele właściwości stanu interfejsu.
  • Zmień uiState w StateFlow.
  • Utwórz uiState za pomocą operatora stateIn z zasadami WhileSubscribed(5000), jeśli dane pochodzą ze strumienia danych z innych warstw hierarchii. (Zobacz ten przykład kodu).
  • W prostszych przypadkach, gdy z warstwy danych nie pochodzą żadne strumienie danych, można użyć MutableStateFlow udostępnionego jako niezmienny StateFlow.
  • Możesz wybrać ${Screen}UiState jako klasę danych, która może zawierać dane, błędy i sygnały wczytywania. Jeśli różne stany się wykluczają, klasa może być też klasą zamkniętą.

Poniższy fragment kodu pokazuje, jak udostępnić stan interfejsu z klasy ViewModel:

@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
            )

    // ...
}

Cykl życia

Postępuj zgodnie ze sprawdzonymi metodami dotyczącymi cyklu życia aktywności:

Rekomendacja Opis
Używaj w kompozycjach efektów uwzględniających cykl życia zamiast zastępować wywołania zwrotne cyklu życia Activity.

Nie zastępuj Activity metod cyklu życia, takich jak onResume, aby wykonywać zadania związane z interfejsem. Zamiast tego używaj LifecycleEffects w Compose lub zakresów współprogramów uwzględniających cykl życia:

Poniższy fragment kodu pokazuje, jak wykonywać operacje w określonym stanie cyklu życia:

  @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)
        }
    }
  }

Obsługa zależności

Podczas zarządzania zależnościami między komponentami postępuj zgodnie ze sprawdzonymi metodami:

Rekomendacja Opis
Używaj wstrzykiwania zależności. Stosuj sprawdzone metody wstrzykiwania zależności, a w miarę możliwości korzystaj głównie z wstrzykiwania przez konstruktor.
W razie potrzeby ogranicz zakres do komponentu. Ogranicz zakres do kontenera zależności, gdy typ zawiera modyfikowalne dane, które muszą być udostępniane, lub gdy typ jest kosztowny w inicjowaniu i jest szeroko stosowany w aplikacji.
Użyj Hilt. W prostych aplikacjach używaj Hilta lub ręcznego wstrzykiwania zależności. Używaj Hilta, jeśli Twój projekt jest wystarczająco złożony, np. zawiera któryś z tych elementów:
  • Wiele ekranów z obiektami ViewModel
  • Korzystanie z biblioteki WorkManager
  • ma obiekty ViewModel powiązane z elementami na stosie wstecznej nawigacji;

Testowanie

Oto kilka sprawdzonych metod testowania:

Rekomendacja Opis
Wiedzieć, co testować

Jeśli projekt nie jest tak prosty jak aplikacja „hello world”, przetestuj go. Powinny one zawierać co najmniej te informacje:

  • Testy jednostkowe dla klas ViewModel, w tym Flow
  • testy jednostkowe dla elementów warstwy danych, czyli repozytoriów i źródeł danych;
  • Testy nawigacji w interfejsie, które są przydatne jako testy regresyjne w CI
Preferuj obiekty zastępcze zamiast atrap. Więcej informacji o używaniu obiektów zastępczych znajdziesz w artykule Używanie obiektów zastępczych w Androidzie.
Testowanie StateFlows. Podczas testowania StateFlow wykonaj te czynności:

Więcej informacji znajdziesz w artykułach Co testować na AndroidzieTestowanie układu Compose.

Modele

Podczas tworzenia modeli w aplikacjach stosuj te sprawdzone metody:

Rekomendacja Opis
W przypadku złożonych aplikacji utwórz model dla każdej warstwy.

W przypadku złożonych aplikacji twórz nowe modele w różnych warstwach lub komponentach, gdy ma to sens. Oto przykłady:

  • Zdalne źródło danych może mapować model otrzymywany przez sieć na prostszą klasę zawierającą tylko dane potrzebne aplikacji.
  • Repozytoria mogą mapować modele DAO na prostsze klasy danych, które zawierają tylko informacje potrzebne warstwie interfejsu.
  • ViewModel może zawierać modele warstwy danych w klasach UiState.

Konwencje nazewnictwa

Podczas nazywania bazy kodu warto pamiętać o tych sprawdzonych metodach:

Rekomendacja Opis
Metody nazywania.
Opcjonalny
Do nazywania metod używaj wyrażeń czasownikowych, np. makePayment().
Nazewnictwo właściwości.
Opcjonalny
Do nazywania właściwości używaj wyrażeń rzeczownikowych, np. inProgressTopicSelection.
Nazewnictwo strumieni danych.
Opcjonalny
Gdy klasa udostępnia strumień Flow lub inny strumień, obowiązuje konwencja nazewnictwa get{model}Stream. Na przykład: getAuthorStream(): Flow<Author>. Jeśli funkcja zwraca listę modeli, użyj nazwy modelu w liczbie mnogiej: getAuthorsStream(): Flow<List<Author>>.
Nazewnictwo implementacji interfejsów.
Opcjonalny
Nadawaj implementacjom interfejsów rozpoznawalne nazwy. Jeśli nie można znaleźć lepszej nazwy, użyj prefiksu Default. Na przykład w przypadku interfejsu NewsRepository możesz mieć OfflineFirstNewsRepository lub InMemoryNewsRepository. Jeśli nie możesz znaleźć odpowiedniej nazwy, użyj DefaultNewsRepository. Prefiksuj fałszywe implementacje symbolem Fake, np. FakeAuthorsRepository.

Dodatkowe materiały

Więcej informacji o architekturze Androida znajdziesz w tych materiałach:

Dokumentacja

Wyświetlanie treści