Na tej stronie znajdziesz kilka sprawdzonych metod i zaleceń dotyczących architektury. Stosuj je, aby poprawić jakość, niezawodność i skalowalność aplikacji. Ułatwiają też utrzymanie i testowanie aplikacji.
Poniższe sprawdzone metody są pogrupowane według tematu. Każdy z nich ma priorytet, który odzwierciedla, jak bardzo zespół go zaleca. Lista priorytetów:
- Zalecane: należy wdrożyć tę metodę, chyba że jest ona sprzeczna z Twoim podejściem.
- Zalecane: ta metoda prawdopodobnie poprawi jakość aplikacji.
- Opcjonalnie: w pewnych okolicznościach ta praktyka może poprawić działanie aplikacji.
Architektura warstwowa
Nasza zalecana architektura warstwowa preferuje dzielenie potencjalnych problemów. Interfejs użytkownika jest generowany na podstawie modeli danych, jest zgodny z zasadą pojedynczego źródła informacji oraz przestrzega zasad jednokierunkowego przepływu danych. Oto kilka sprawdzonych metod w przypadku architektury warstwowej:
| Rekomendacja | Opis | 
|---|---|
| Używaj wyraźnie zdefiniowanej warstwy danych. Zdecydowanie zalecane | Warstwa danych udostępnia dane aplikacji reszcie aplikacji i zawiera zdecydowaną większość logiki biznesowej aplikacji. 
 | 
| Używaj wyraźnie zdefiniowanej warstwy interfejsu użytkownika. Zdecydowanie zalecane | Warstwa UI wyświetla na ekranie dane aplikacji i służy jako główny punkt interakcji z użytkownikiem. 
 | 
| Warstwa danych powinna udostępniać dane aplikacji za pomocą repozytorium. Zdecydowanie zalecane | Komponenty w warstwie interfejsu, takie jak obiekty kompozycyjne, aktywności czy ViewModels, nie powinny wchodzić w bezpośrednią interakcję ze źródłem danych. Przykłady źródeł danych: 
 | 
| Używaj korobon i przepływów. Zdecydowanie zalecane | Do komunikacji między warstwami używaj korobon i przepływów. | 
| Użyj warstwy domeny. Zalecane w dużych aplikacjach | Użyj warstwy domeny, jeśli chcesz ponownie użyć logiki biznesowej, która współdziała z warstwą danych w różnych widokach ViewModel, lub jeśli chcesz uprościć złożoność logiki biznesowej w konkretnym widoku ViewModel. | 
Warstwa interfejsu
Rola warstwy interfejsu polega na wyświetlaniu danych aplikacji na ekranie i służeniu za główny punkt interakcji użytkownika. Oto kilka sprawdzonych metod dotyczących warstwy UI:
| Rekomendacja | Opis | 
|---|---|
| Postępuj zgodnie z jednokierunkowym przepływem danych (UDF). Zdecydowanie zalecane | Stosuj zasady jednokierunkowego przepływu danych (UDF), w których ViewModels udostępnia stan interfejsu użytkownika za pomocą wzorca obserwatora i odbiera działania z interfejsu użytkownika przez wywołania metod. | 
| Użyj modeli ViewModel w AAC, jeśli ich zalety pasują do Twojej aplikacji. Zdecydowanie zalecane | Użyj ViewModels AAC, aby obsługiwać logikę biznesową, i pobierz dane aplikacji, aby ujawnić stan interfejsu użytkownika (Compose lub widoki Androida). Więcej sprawdzonych metod dotyczących ViewModel znajdziesz tutaj. | 
| Używaj kolekcji stanu interfejsu uwzględniającej cykl życia. Zdecydowanie zalecane | Zbieraj stan interfejsu z interfejsu za pomocą odpowiedniego kreatora coroutine uwzględniającego cykl życia: repeatOnLifecyclew systemie View icollectAsStateWithLifecyclew Jetpack Compose.Dowiedz się więcej o   Dowiedz się więcej na ten temat:  | 
| Nie wysyłaj zdarzeń z ViewModel do interfejsu użytkownika. Zdecydowanie zalecane | Przetwórz zdarzenie natychmiast w modelu ViewModel i wywołaj aktualizację stanu w wyniku obsługi zdarzenia. Więcej informacji o zdarzeniach interfejsu użytkownika. | 
| Użyj aplikacji z jednym działaniem. Zalecane | Jeśli Twoja aplikacja ma więcej niż 1 ekran, do poruszania się między ekranami i precyzyjnych linków do aplikacji używaj fragmentów nawigacji lub kompozycji nawigacyjnych. | 
| Użyj Jetpack Compose. Zalecane | Za pomocą Jetpack Compose możesz tworzyć nowe aplikacje na telefony, tablety, urządzenia składane i Wear OS. | 
Ten fragment kodu pokazuje, jak pobierać stan interfejsu użytkownika z uwzględnieniem cyklu życia:
Wyświetlenia
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
                }
            }
        }
    }
}
Compose
@Composable
fun MyScreen(
    viewModel: MyViewModel = viewModel()
) {
    val uiState by viewModel.uiState.collectAsStateWithLifecycle()
}
ViewModel
Obiekty ViewModels odpowiadają za udostępnianie stanu interfejsu użytkownika i dostępu do warstwy danych. Oto kilka sprawdzonych metod dotyczących widoków modelu:
| Rekomendacja | Opis | 
|---|---|
| Modele widoku danych powinny być niezależne od cyklu życia Androida. Zdecydowanie zalecane | Obiekty ViewModels nie powinny zawierać odwołania do żadnego typu powiązanego z cyklem życia. Nie przekazuj wartości Activity, Fragment, ContextaniResourcesjako zależności.
Jeśli coś wymagaContextw modelu widoku, musisz zdecydowanie sprawdzić, czy znajduje się on we właściwej warstwie. | 
| Używaj korobon i przepływów. Zdecydowanie zalecane | Model widoku danych współdziała z warstwami danych lub domeny za pomocą: 
 | 
| Używaj widoków ViewModel na poziomie ekranu. Zdecydowanie zalecane | Nie używaj widoków w elementach interfejsu użytkownika, które można ponownie wykorzystać. ViewModels należy używać w tych sytuacjach: 
 | 
| W komponentach UI do wielokrotnego użytku używaj zwykłych klas uchwytów stanu. Zdecydowanie zalecane | Aby poradzić sobie z zaawansowanymi funkcjami w wielokrotnie używanych komponentach UI, użyj zwykłych klas przechowujących stan. Dzięki temu stan może być podnoszony i kontrolowany zewnętrznie. | 
| Nie używaj AndroidViewModel.Zalecane | Użyj klasy ViewModel, a nieAndroidViewModel. KlasyApplicationnie należy używać w klasie ViewModel. Zamiast tego przenieś zależność do interfejsu użytkownika lub warstwy danych. | 
| udostępniać stan interfejsu, Zalecane | Modele widoku powinny udostępniać dane interfejsowi za pomocą jednej właściwości o nazwie uiState. Jeśli interfejs użytkownika wyświetla wiele niezwiązanych ze sobą elementów danych, maszyna wirtualna może wyświetlać wiele właściwości stanu interfejsu użytkownika.
 | 
Ten fragment kodu pokazuje, jak ujawnić stan UI z interfejsu 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
Oto kilka sprawdzonych metod dotyczących cyklu życia aplikacji na Androida:
| Rekomendacja | Opis | 
|---|---|
| Nie zastępuj metod cyklu życia w aktywnościach ani fragmentach. Zdecydowanie zalecane | Nie zastępuj metod cyklu życia, takich jak onResume, w działaniach lub fragmentach. Zamiast niego użyjLifecycleObserver. Jeśli aplikacja musi wykonać zadanie, gdy cykl życia osiągnie określonyLifecycle.State, użyj interfejsu APIrepeatOnLifecycle. | 
W tym fragmencie kodu opisano, jak wykonywać operacje w zależności od stanu cyklu życia:
Wyświetlenia
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) {
                // ...
            }
        }
    }
}
Compose
@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)
        }
    }
}
Zarządzanie zależnościami
Jest kilka sprawdzonych metod, o których warto pamiętać, zarządzając zależnościami między komponentami:
| Rekomendacja | Opis | 
|---|---|
| Używaj wstrzykiwania zależności. Zdecydowanie zalecane | Stosuj sprawdzone metody korzystania z wstrzykiwania zależności, głównie wstrzykiwania konstruktora, gdy to możliwe. | 
| W razie potrzeby ogranicz zakres do komponentu. Zdecydowanie zalecane | Ogranicz dostęp do kontenera zależności, gdy typ zawiera dane, które można zmienić i które trzeba udostępnić, lub gdy typ jest kosztowny w inicjalizacji i jest często używany w aplikacji. | 
| Użyj Hilt. Zalecane | W prostych aplikacjach używaj Hilt lub ręcznego wstrzykiwania zależności. Użyj Hilt, jeśli Twój projekt jest na tyle złożony. Jeśli na przykład masz: 
 | 
Testowanie
Oto kilka sprawdzonych metod dotyczących testowania:
| Rekomendacja | Opis | 
|---|---|
| Co warto przetestować. Zdecydowanie zalecane | Jeśli projekt nie jest tak prosty jak aplikacja „hello world”, należy go przetestować, używając co najmniej: 
 | 
| preferować fałszywe treści zamiast mockupów. Zdecydowanie zalecane | Więcej informacji znajdziesz w dokumentacji Androida na temat używania podwójnych testów. | 
| Testowanie StateFlow. Zdecydowanie zalecane | Podczas testowania StateFlow:
 | 
Więcej informacji znajdziesz w przewodniku DAC na temat Androida.
Modele
Podczas tworzenia modeli w aplikacjach należy przestrzegać tych sprawdzonych metod:
| Rekomendacja | Opis | 
|---|---|
| tworzenie modelu na warstwę w skomplikowanych aplikacjach. Zalecane | W skomplikowanych aplikacjach, gdy ma to sens, twórz nowe modele na różnych warstwach lub komponentach. Zapoznaj się z tymi przykładami: 
 | 
Konwencje nazewnictwa
Podczas nadawania nazwy kodowi źródłowemu pamiętaj o tych sprawdzonych metodach:
| Rekomendacja | Opis | 
|---|---|
| Nazwy metod. Opcjonalnie | Metody powinny być wyrażeniem czasownikowym. Na przykład: makePayment(). | 
| Nazywanie właściwości. Opcjonalny | Właściwości powinny być wyrażeniami rzeczownikowymi. Na przykład: inProgressTopicSelection. | 
| Nazywanie strumieni danych. Opcjonalnie | Gdy klasa ujawnia strumień Flow, LiveData lub dowolny inny strumień, konwencja nazewnictwa jest zgodna z konwencją get{model}Stream(). Na przykładgetAuthorStream(): Flow<Author>Jeśli funkcja zwraca listę modeli, nazwa modelu powinna być w liczbie mnogiej:getAuthorsStream(): Flow<List<Author>> | 
| Implementacje interfejsów nazewnictwa. Opcjonalnie | Nazwy implementacji interfejsów powinny być zrozumiałe. Jeśli nie możesz znaleźć lepszej nazwy, użyj prefiksu Default. Na przykład w przypadku interfejsuNewsRepositorymożesz miećOfflineFirstNewsRepositorylubInMemoryNewsRepository. Jeśli nie możesz znaleźć odpowiedniej nazwy, użyjDefaultNewsRepository.
    Fałszywe implementacje powinny mieć prefiksFake, np.FakeAuthorsRepository. | 
