Pojęcia i implementacja w Jetpack Compose
Interfejs użytkownika służy do wyświetlania danych aplikacji na ekranie, a także jako główny punkt interakcji użytkownika. Gdy dane się zmienią, czy to w wyniku interakcji użytkownika (np. naciśnięcia przycisku), czy danych wejściowych z zewnątrz (np. odpowiedzi sieci), interfejs powinien się zaktualizować, aby odzwierciedlić te zmiany. Interfejs użytkownika jest wizualną reprezentacją stanu aplikacji pobranego z warstwy danych.
Dane aplikacji, które uzyskujesz z warstwy danych, mają jednak zwykle inny format niż informacje, które chcesz wyświetlać. Możesz na przykład potrzebować tylko części danych do interfejsu lub połączyć 2 różne źródła danych, aby wyświetlić informacje istotne dla użytkownika. Niezależnie od zastosowanej logiki musisz przekazać interfejsowi wszystkie informacje potrzebne do pełnego renderowania. Warstwa interfejsu to potok, który przekształca zmiany danych aplikacji w formę, którą interfejs może prezentować, a następnie wyświetla te dane.
Udostępnianie stanu interfejsu
Po zdefiniowaniu stanu interfejsu i określeniu sposobu zarządzania jego produkcją kolejnym krokiem jest przedstawienie wyprodukowanego stanu w interfejsie. Ponieważ do zarządzania stanem produkcji używasz funkcji UDF, możesz traktować wygenerowany stan jako strumień, czyli z czasem będzie powstawać wiele wersji stanu. W związku z tym stan interfejsu należy udostępniać w obserwowalnym kontenerze danych, takim jak LiveData lub StateFlow. Dzięki temu interfejs może reagować na wszelkie zmiany stanu bez konieczności ręcznego pobierania danych bezpośrednio z ViewModelu. Ten typ ma też tę zaletę, że zawsze ma w pamięci podręcznej najnowszą wersję stanu interfejsu, co jest przydatne do szybkiego przywracania stanu po zmianach konfiguracji.
class NewsViewModel(...) : ViewModel() {
val uiState: StateFlow<NewsUiState> = …
}
Częstym sposobem tworzenia strumienia UiState jest udostępnianie strumienia modyfikowalnego jako strumienia niemodyfikowalnego z poziomu ViewModel – na przykład udostępnianie MutableStateFlow<UiState> jako StateFlow<UiState>.
class NewsViewModel(...) : ViewModel() {
private val _uiState = MutableStateFlow(NewsUiState())
val uiState: StateFlow<NewsUiState> = _uiState.asStateFlow()
...
}
ViewModel może następnie udostępniać metody, które wewnętrznie zmieniają stan, publikując aktualizacje, które UI może wykorzystywać. Rozważmy na przykład sytuację, w której trzeba wykonać działanie asynchroniczne. Można uruchomić współprogram za pomocą funkcji viewModelScope, a po zakończeniu działania zaktualizować stan modyfikowalny.
class NewsViewModel(
private val repository: NewsRepository,
...
) : ViewModel() {
private val _uiState = MutableStateFlow(NewsUiState())
val uiState: StateFlow<NewsUiState> = _uiState.asStateFlow()
private var fetchJob: Job? = null
fun fetchArticles(category: String) {
fetchJob?.cancel()
fetchJob = viewModelScope.launch {
try {
val newsItems = repository.newsItemsForCategory(category)
_uiState.update {
it.copy(newsItems = newsItems)
}
} catch (ioe: IOException) {
// Handle the error and notify the UI when appropriate.
_uiState.update {
val messages = getMessagesFromThrowable(ioe)
it.copy(userMessages = messages)
}
}
}
}
}
Korzystanie ze stanu interfejsu
Podczas korzystania z obserwowalnych obiektów danych w interfejsie użytkownika weź pod uwagę cykl życia interfejsu. Jest to ważne, ponieważ interfejs nie powinien obserwować stanu interfejsu, gdy widok nie jest wyświetlany użytkownikowi. Więcej informacji na ten temat znajdziesz w tym poście na blogu.
Gdy używasz LiveData, LifecycleOwner automatycznie zajmuje się kwestiami związanymi z cyklem życia. W przypadku korzystania z przepływów najlepiej jest obsługiwać to za pomocą odpowiedniego zakresu współprogramu i repeatOnLifecycle interfejsu API:
class NewsActivity : AppCompatActivity() {
private val viewModel: NewsViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
...
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect {
// Update UI elements
}
}
}
}
}
Pokaż operacje w toku
Prostym sposobem na przedstawienie stanów ładowania w klasie UiState jest pole logiczne:
data class NewsUiState(
val isFetchingArticles: Boolean = false,
...
)
Wartość tego flagi określa, czy w interfejsie jest widoczny pasek postępu.
class NewsActivity : AppCompatActivity() {
private val viewModel: NewsViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
...
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
// Bind the visibility of the progressBar to the state
// of isFetchingArticles.
viewModel.uiState
.map { it.isFetchingArticles }
.distinctUntilChanged()
.collect { progressBar.isVisible = it }
}
}
}
}
Animacje
Aby zapewnić płynne przejścia w nawigacji najwyższego poziomu, możesz poczekać, aż drugi ekran wczyta dane, zanim rozpoczniesz animację.
Platforma widoku Androida udostępnia punkty zaczepienia do opóźniania przejść między miejscami docelowymi fragmentów za pomocą interfejsów API postponeEnterTransition() i startPostponedEnterTransition(). Te interfejsy API zapewniają, że elementy interfejsu na drugim ekranie (zwykle obraz pobrany z sieci) są gotowe do wyświetlenia, zanim interfejs użytkownika rozpocznie animację przejścia do tego ekranu.
Polecane dla Ciebie
- Uwaga: tekst linku jest wyświetlany, gdy język JavaScript jest wyłączony.
- Produkcja stanu interfejsu
- Obiekty stanu i stan interfejsu {:#mad-arch}
- Przewodnik po architekturze aplikacji