W Compose interfejs jest niezmienny – nie można go zaktualizować po narysowaniu. Możesz kontrolować stan interfejsu. Za każdym razem, gdy zmienia się stan interfejsu, Compose odtworzy części drzewa interfejsu, które uległy zmianie. Funkcje kompozycyjne mogą akceptować stan i udostępniać zdarzenia. Na przykład funkcja TextField akceptuje wartość i udostępnia wywołanie zwrotne onValueChange, które wysyła do procedury obsługi wywołania zwrotnego żądanie zmiany wartości.
var name by remember { mutableStateOf("") } OutlinedTextField( value = name, onValueChange = { name = it }, label = { Text("Name") } )
Funkcje kompozycyjne akceptują stan i udostępniają zdarzenia, dlatego jednokierunkowy przepływ danych dobrze pasuje do Jetpack Compose. Ten przewodnik skupia się na tym, jak w Compose zaimplementować wzorzec jednokierunkowego przepływu danych, jak zaimplementować zdarzenia i obiekty przechowujące stan oraz jak pracować z obiektami ViewModel w Compose.
Jednokierunkowy przepływ danych
Jednokierunkowy przepływ danych (UDF) to wzorzec projektowy, w którym stan przepływa w dół, a zdarzenia w górę. Stosując jednokierunkowy przepływ danych, możesz oddzielić komponenty kompozycyjne, które wyświetlają stan w interfejsie, od części aplikacji, które przechowują i zmieniają stan.
Pętla aktualizacji interfejsu aplikacji korzystającej z jednokierunkowego przepływu danych wygląda tak:
- Zdarzenie: część interfejsu generuje zdarzenie i przekazuje je wyżej, np. kliknięcie przycisku przekazywane do ViewModel w celu obsługi, lub zdarzenie jest przekazywane z innych warstw aplikacji, np. informujące o wygasnięciu sesji użytkownika.
- Aktualizacja stanu: moduł obsługi zdarzeń może zmienić stan.
- Stan wyświetlania: obiekt przechowujący stan przekazuje stan, a interfejs użytkownika go wyświetla.
Stosowanie tego wzorca podczas korzystania z Jetpack Compose ma kilka zalet:
- Możliwość testowania: oddzielenie stanu od interfejsu, który go wyświetla, ułatwia testowanie obu tych elementów osobno.
- Hermetyzacja stanu: ponieważ stan można aktualizować tylko w jednym miejscu i jest tylko jedno źródło informacji o stanie komponentu, jest mniejsze prawdopodobieństwo, że wystąpią błędy spowodowane niespójnymi stanami.
- Spójność interfejsu: wszystkie aktualizacje stanu są natychmiast odzwierciedlane w interfejsie dzięki użyciu obiektów stanu obserwowanego, takich jak
StateFlowlubLiveData.
Jednokierunkowy przepływ danych w Jetpack Compose
Komponenty kompozycyjne działają na podstawie stanu i zdarzeń. Na przykład TextField jest aktualizowany tylko wtedy, gdy aktualizowany jest jego parametr value, i udostępnia wywołanie zwrotne onValueChange – zdarzenie, które żąda zmiany wartości na nową. Funkcja Compose definiuje obiekt State jako pojemnik wartości, a zmiany wartości stanu powodują ponowne komponowanie. Stan możesz przechowywać w remember { mutableStateOf(value) } lub rememberSaveable { mutableStateOf(value) w zależności od tego, jak długo musisz pamiętać wartość.
Typ wartości funkcji TextField to String, więc może ona pochodzić z dowolnego miejsca – z wartości zakodowanej na stałe, z obiektu ViewModel lub z funkcji kompozycyjnej wyższego rzędu. Nie musisz przechowywać go w obiekcie State, ale musisz zaktualizować wartość, gdy wywoływana jest funkcja onValueChange.
Definiowanie parametrów kompozycyjnych
Podczas definiowania parametrów stanu funkcji kompozycyjnej pamiętaj o tych pytaniach:
- Jak bardzo komponent jest wielokrotnego użytku i elastyczny?
- Jak parametry stanu wpływają na wydajność tego komponentu?
Aby promować rozdzielanie i ponowne wykorzystywanie, każdy element kompozycyjny powinien zawierać jak najmniej informacji. Na przykład podczas tworzenia komponentu kompozycyjnego, który będzie zawierać nagłówek artykułu z wiadomościami, lepiej przekazywać tylko informacje, które mają być wyświetlane, zamiast całego artykułu:
@Composable fun Header(title: String, subtitle: String) { // Recomposes when title or subtitle have changed. } @Composable fun Header(news: News) { // Recomposes when a new instance of News is passed in. }
Czasami używanie poszczególnych parametrów też poprawia wydajność – na przykład jeśli News zawiera więcej informacji niż tylko title i subtitle, za każdym razem, gdy do Header(news) przekazywana jest nowa instancja News, funkcja kompozycyjna zostanie ponownie skomponowana, nawet jeśli title i subtitle nie uległy zmianie.
Starannie rozważ liczbę przekazywanych parametrów. Funkcja ze zbyt dużą liczbą parametrów zmniejsza ergonomię funkcji, więc w tym przypadku preferowane jest zgrupowanie ich w klasie.
Wydarzenia w komponowaniu
Każde działanie w aplikacji powinno być reprezentowane jako zdarzenie: kliknięcia, zmiany tekstu, a nawet timery lub inne aktualizacje. Ponieważ te zdarzenia zmieniają stan interfejsu, powinny być obsługiwane przez ViewModel, który będzie aktualizować stan interfejsu.
Warstwa interfejsu nie powinna nigdy zmieniać stanu poza procedurą obsługi zdarzeń, ponieważ może to powodować niespójności i błędy w aplikacji.
W przypadku lambd stanu i obsługi zdarzeń preferuj przekazywanie wartości niezmiennych. Takie podejście ma następujące zalety:
- Zwiększasz możliwość ponownego wykorzystania.
- Sprawdź, czy interfejs nie zmienia bezpośrednio wartości stanu.
- Unikasz problemów z jednoczesnością, ponieważ dbasz o to, aby stan nie był modyfikowany przez inny wątek.
- Często zmniejszasz złożoność kodu.
Na przykład funkcję kompozycyjną, która przyjmuje jako parametry String i funkcję lambda, można wywoływać w wielu kontekstach i jest ona wysoce wielokrotnego użytku. Załóżmy, że górny pasek aplikacji w Twojej aplikacji zawsze wyświetla tekst i ma przycisk Wstecz. Możesz zdefiniować bardziej ogólny MyAppTopAppBar composable
który przyjmuje tekst i uchwyt przycisku Wstecz jako parametry:
@Composable fun MyAppTopAppBar(topAppBarText: String, onBackPressed: () -> Unit) { TopAppBar( title = { Text( text = topAppBarText, textAlign = TextAlign.Center, modifier = Modifier .fillMaxSize() .wrapContentSize(Alignment.Center) ) }, navigationIcon = { IconButton(onClick = onBackPressed) { Icon( Icons.AutoMirrored.Filled.ArrowBack, contentDescription = localizedString ) } }, // ... ) }
ViewModel, stany i zdarzenia: przykład
Używając ViewModel i mutableStateOf, możesz też wprowadzić w aplikacji jednokierunkowy przepływ danych, jeśli spełniony jest jeden z tych warunków:
- Stan interfejsu jest udostępniany za pomocą obiektów stanu obserwowalnego, takich jak
StateFlowczyLiveData. ViewModelobsługuje zdarzenia pochodzące z interfejsu lub innych warstw aplikacji i aktualizuje obiekt stanu na podstawie tych zdarzeń.
Na przykład podczas implementowania ekranu logowania kliknięcie przycisku Zaloguj się powinno spowodować wyświetlenie w aplikacji wskaźnika postępu i wykonanie połączenia sieciowego. Jeśli logowanie się powiedzie, aplikacja przejdzie do innego ekranu. W przypadku błędu wyświetli Snackbar. Oto jak modelować stan ekranu i zdarzenie:
Ekran może być w jednym z 4 stanów:
- Wylogowany: gdy użytkownik nie zalogował się jeszcze w usłudze.
- W trakcie: gdy aplikacja próbuje zalogować użytkownika, wykonując wywołanie sieciowe.
- Błąd: gdy podczas logowania wystąpił błąd.
- Zalogowany: gdy użytkownik jest zalogowany.
Te stany możesz modelować jako klasę zamkniętą. ViewModel udostępnia stan jako State, ustawia stan początkowy i w razie potrzeby go aktualizuje. ViewModel obsługuje też zdarzenie logowania, udostępniając metodę onSignIn().
class MyViewModel : ViewModel() { private val _uiState = mutableStateOf<UiState>(UiState.SignedOut) val uiState: State<UiState> get() = _uiState // ... }
Oprócz mutableStateOfinterfejsu API Compose udostępnia rozszerzenia dla LiveData, Flow i Observable, które umożliwiają rejestrowanie się jako odbiorca i przedstawianie wartości jako stanu.
class MyViewModel : ViewModel() { private val _uiState = MutableLiveData<UiState>(UiState.SignedOut) val uiState: LiveData<UiState> get() = _uiState // ... } @Composable fun MyComposable(viewModel: MyViewModel) { val uiState = viewModel.uiState.observeAsState() // ... }
Więcej informacji
Więcej informacji o architekturze w Jetpack Compose znajdziesz w tych materiałach:
Próbki
Polecane dla Ciebie
- Uwaga: tekst linku jest wyświetlany, gdy język JavaScript jest wyłączony.
- Stan i Jetpack Compose
- Zapisywanie stanu interfejsu w Compose
- Obsługa danych wejściowych użytkownika