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 ponownie tworzy te części drzewa interfejsu, które uległy zmianie. Komponenty kompozycyjne mogą akceptować stan i udostępniać zdarzenia – na przykład TextField akceptuje wartość i udostępnia wywołanie zwrotne onValueChange, które żąda od modułu obsługi wywołania zwrotnego zmiany wartości.
var name by remember { mutableStateOf("") } OutlinedTextField( value = name, onValueChange = { name = it }, label = { Text("Name") } )
Ponieważ funkcje kompozycyjne akceptują stan i udostępniają zdarzenia, jednokierunkowy przepływ danych dobrze pasuje do Jetpack Compose. Z tego przewodnika dowiesz się, jak wdrożyć wzorzec jednokierunkowego przepływu danych w Compose, jak wdrożyć zdarzenia i zmienne stanu oraz jak pracować z ViewModelami 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ę. Dzięki jednokierunkowemu przepływowi danych możesz oddzielić komponenty, które wyświetlają stan w interfejsie, od części aplikacji, które przechowują i zmieniają stan.
Pętla aktualizacji interfejsu w aplikacji korzystającej z jednokierunkowego przepływu danych wygląda tak:
- Zdarzenie: część interfejsu generuje zdarzenie i przekazuje je w górę, np. kliknięcie przycisku przekazywane do ViewModel do obsługi, lub zdarzenie jest przekazywane z innych warstw aplikacji, np. wskazujące, że sesja użytkownika wygasła.
- Aktualizacja stanu: moduł obsługi zdarzeń może zmienić stan.
- Stan wyświetlania: zmienna stanu przekazuje stan, a interfejs go wyświetla.
Stosowanie tego wzorca podczas korzystania z Jetpack Compose zapewnia kilka korzyści:
- Możliwość testowania: oddzielenie stanu od interfejsu, który go wyświetla, ułatwia testowanie obu tych elementów w izolacji.
- Enkapsulacja stanu: ponieważ stan można aktualizować tylko w jednym miejscu i istnieje tylko jedno źródło informacji o stanie funkcji kompozycyjnej, jest mniej prawdopodobne, że utworzysz błędy z powodu niespójnych stanów.
- Spójność interfejsu: wszystkie aktualizacje stanu są natychmiast odzwierciedlane w interfejsie dzięki użyciu obserwowalnych zmiennych stanu, 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ą. Compose definiuje obiekt State jako obiekt przechowujący wartość, a zmiany wartości stanu powodują ponowne tworzenie. Stan możesz przechowywać w obiekcie remember { mutableStateOf(value) } lub rememberSaveable { mutableStateOf(value) } w zależności od tego, jak długo chcesz pamiętać wartość.
Typ wartości funkcji kompozycyjnej TextField to String, więc może ona pochodzić z dowolnego miejsca – z zakodowanej na stałe wartości, z ViewModel lub z nadrzędnej funkcji kompozycyjnej. Nie musisz przechowywać jej w obiekcie State, ale musisz zaktualizować wartość, gdy wywoływana jest metoda onValueChange.
mutableStateOf(value)tworzyMutableState, który jest obserwowalnym typem w Compose. Wszelkie zmiany wartości powodują ponowne tworzenie wszystkich funkcji typu „composable”, które odczytują tę wartość.rememberprzechowuje obiekty w kompozycji i zapomina o nich, gdy element kompozycyjny, który wywołałremember, zostanie usunięty z kompozycji.rememberSaveablezachowuje stan po zmianach konfiguracji, zapisując go wBundle.
Definiowanie parametrów funkcji kompozycyjnych
Podczas definiowania parametrów stanu funkcji kompozycyjnej pamiętaj o tych pytaniach:
- Jak bardzo dany element kompozycyjny nadaje się do wielokrotnego użytku i jak bardzo jest elastyczny?
- Jak parametry stanu wpływają na wydajność tego elementu kompozycyjnego?
Aby promować rozdzielenie i ponowne użycie, każda funkcja kompozycyjna powinna zawierać jak najmniej informacji. Jeśli na przykład tworzysz element kompozycyjny, który ma zawierać nagłówek artykułu, lepiej przekazać tylko te informacje, które mają być wyświetlane, a nie cały artykuł:
@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życie poszczególnych parametrów poprawia też wydajność – na przykład jeśli News zawiera więcej informacji niż tylko title i subtitle, za każdym razem, gdy nowa instancja News jest przekazywana do Header(news), funkcja kompozycyjna zostanie ponownie skomponowana, nawet jeśli title i subtitle nie uległy zmianie.
Zastanów się dokładnie nad liczbą przekazywanych parametrów. Funkcja ze zbyt dużą liczbą parametrów jest mniej ergonomiczna, dlatego w takim przypadku lepiej jest pogrupować je w klasę.
Zdarzenia w narzędziu do tworzenia
Każde działanie w aplikacji powinno być reprezentowane jako zdarzenie: kliknięcia, zmiany tekstu, a nawet liczniki czasu i inne aktualizacje. Ponieważ te zdarzenia zmieniają stan interfejsu, ViewModel powinien je obsługiwać i aktualizować stan interfejsu.
Warstwa interfejsu nigdy nie powinna zmieniać stanu poza modułem obsługi zdarzeń, ponieważ może to powodować niespójności i błędy w aplikacji.
Zalecamy przekazywanie niezmiennych wartości dla lambd stanu i modułu obsługi zdarzeń. Ta metoda ma następujące zalety:
- Zwiększasz możliwość ponownego wykorzystania.
- Sprawdzasz, czy interfejs nie zmienia bezpośrednio wartości stanu.
- Unikasz problemów ze współbieżnością, ponieważ masz pewność, że stan nie jest zmieniany z innego wątku.
- Często zmniejsza to złożoność kodu.
Na przykład element kompozycyjny, który akceptuje String i funkcję lambda jako parametry, może być wywoływany z wielu kontekstów i jest bardzo przydatny do wielokrotnego wykorzystania. Załóżmy, że górny pasek aplikacji
zawsze wyświetla tekst i ma przycisk Wstecz. Możesz zdefiniować bardziej ogólny element kompozycyjny MyAppTopAppBar, który otrzymuje tekst i moduł obsługi 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 ) } }, // ... ) }
ViewModels, 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ą obserwowalnych zmiennych stanu, takich jak
StateFlowlubLiveData. ViewModelobsługuje zdarzenia pochodzące z interfejsu lub innych warstw aplikacji i na ich podstawie aktualizuje zmienną stanu.
Na przykład podczas implementowania ekranu logowania kliknięcie przycisku Zaloguj się powinno spowodować wyświetlenie w aplikacji wskaźnika postępu i wywołanie sieciowe. Jeśli logowanie się powiedzie, aplikacja przejdzie do innego ekranu. W przypadku błędu aplikacja wyświetli pasek z powiadomieniem. Oto jak możesz modelować stan ekranu i zdarzenie:
Ekran ma 4 stany:
- Wylogowany: gdy użytkownik nie jest jeszcze zalogowany.
- W toku: gdy aplikacja próbuje zalogować użytkownika, wykonując połączenie sieciowe.
- Error (Błąd): gdy podczas logowania wystąpił błąd.
- Zalogowany: gdy użytkownik jest zalogowany.
Możesz modelować te stany jako klasę zapieczętowaną. ViewModel udostępnia stan jako State, ustawia stan początkowy i w razie potrzeby aktualizuje stan. 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 interfejsu API mutableStateOf Compose udostępnia rozszerzenia dla LiveData, Flow i Observable, które umożliwiają rejestrowanie się jako detektor i reprezentowanie 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:
Przykłady
Polecane dla Ciebie
- Uwaga: tekst linku jest wyświetlany, gdy język JavaScript jest wyłączony.
- State i Jetpack Compose
- Zapisywanie stanu interfejsu w Compose
- Obsługa danych wejściowych użytkownika