Zalecenia dotyczące architektury Androida (widoki)

Pojęcia i implementacja w Jetpack Compose

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.

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:

  • Repozytoria należy tworzyć nawet wtedy, gdy zawierają tylko 1 źródło danych.
  • W przypadku małych aplikacji możesz umieścić typy warstwy danych w datapakiecie lub module.

Rekomendacja

Opis

Postępuj zgodnie z zasadami jednokierunkowego przepływu danych (UDF).

Zdecydowanie zalecane

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.

Zdecydowanie zalecane

Używaj AAC ViewModels do obsługi logiki biznesowej i pobierania danych aplikacji, aby udostępniać interfejsowi stan interfejsu.

Więcej sprawdzonych metod dotyczących ViewModel znajdziesz tutaj.

Korzyści z używania ViewModels znajdziesz tutaj

Korzystaj ze zbierania stanu interfejsu, które uwzględnia cykl życia.

Zdecydowanie zalecane

Zbierz stan interfejsu z interfejsu za pomocą odpowiedniego konstruktora współprogramu uwzględniającego cykl życia, repeatOnLifecycle.

Więcej informacji o repeatOnLifecycle

Nie wysyłaj zdarzeń z ViewModel do interfejsu.

Zdecydowanie zalecane

Natychmiast przetwórz zdarzenie w obiekcie ViewModel i spowoduj aktualizację stanu z wynikiem obsługi zdarzenia. Więcej informacji o zdarzeniach interfejsu znajdziesz tutaj.

Używaj aplikacji z jedną aktywnością.

Polecane

Jeśli Twoja aplikacja ma więcej niż 1 ekran, użyj fragmentów nawigacji, aby przełączać się między ekranami i tworzyć precyzyjne linki do aplikacji.

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

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

ViewModel

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

Rekomendacja

Opis

Obiekty ViewModel powinny być niezależne od cyklu życia Androida.

Zdecydowanie zalecane

Obiekty ViewModel nie powinny zawierać odwołań do żadnego typu związanego z cyklem życia. Nie przekazuj Activity, Fragment, Context ani Resources jako zależności. Jeśli coś wymaga Context w obiekcie ViewModel, dokładnie sprawdź, czy to odpowiednia warstwa.

Używaj współprogramów i przepływów.

Zdecydowanie zalecane

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

  • przepływy Kotlin do odbierania danych aplikacji,
  • suspend do wykonywania działań za pomocą viewModelScope.

Używaj obiektów ViewModel na poziomie ekranu.

Zdecydowanie zalecane

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

  • Działania/fragmenty w widokach
  • miejsc docelowych lub wykresów podczas korzystania z nawigacji Jetpack.

Nie używaj AndroidViewModel.

Zdecydowanie zalecane

Użyj klasy ViewModel, a nie AndroidViewModel. Klasy Application nie należy używać w obiekcie ViewModel. Zamiast tego przenieś zależność do interfejsu lub warstwy danych.

Udostępnij stan interfejsu.

Polecane

Modele widoku powinny udostępniać dane interfejsowi za pomocą pojedynczej właściwości o nazwie uiState. Jeśli interfejs wyświetla wiele niezwiązanych ze sobą danych, ViewModel może udostępniać wiele właściwości stanu interfejsu.

  • Zmień uiState na StateFlow.
  • Jeśli dane pochodzą ze strumienia danych z innych warstw hierarchii, utwórz uiState za pomocą operatora stateIn z zasadami WhileSubscribed(5000) (przykład).
  • 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 ładowania. Jeśli różne stany są wykluczające się, ta 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

Oto kilka sprawdzonych metod pracy z cyklem życia Androida:

Rekomendacja

Opis

Nie zastępuj metod cyklu życia w działaniach ani fragmentach.

Zdecydowanie zalecane

Nie zastępuj metod cyklu życia, takich jak onResume, w działaniach ani fragmentach. Zamiast niej używaj zasady LifecycleObserver. Jeśli aplikacja musi wykonać działanie, gdy cykl życia osiągnie określony Lifecycle.State, użyj interfejsu repeatOnLifecycle API.

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

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) {
                // ...
            }
        }
    }
}