W aplikacji Compose, w którym podnosisz stan interfejsu, zależy to od tego, czy niezbędna jest logika biznesowa lub logika biznesowa. W tym dokumencie opisujemy 2 główne w różnych sytuacjach.
Sprawdzona metoda
Należy przenieść stan interfejsu do najmniejszego wspólnego elementu nadrzędnego między wszystkimi elementów kompozycyjnych, które odczytują go i zapisują. Należy zachować stan najbliżej miejsca, w którym się znajduje i całego świata. Od właściciela stanu ujawniaj konsumentom stały stan i zdarzenia do zmiany stanu.
Najniższy wspólny element nadrzędny może też znajdować się poza kompozycją. Przykład:
podczas przenoszenia stanu w ViewModel
, ponieważ uwzględniana jest logika biznesowa.
Na tej stronie szczegółowo opisujemy tę sprawdzoną metodę i pamiętaj o pewnym zastrzeżeniu.
Typy stanu UI i logika interfejsu
Poniżej znajdziesz definicje typów logiki i stanów interfejsu użytkownika, które są używane w całym dokumencie.
Stan interfejsu
Stan interfejsu to właściwość opisująca interfejs użytkownika. Istnieją 2 typy UI województwo:
- Stan UI ekranu określa, co ma się wyświetlać na ekranie. Na przykład plik
NewsUiState
zajęcia mogą zawierać artykuły z wiadomościami i inne potrzebne informacje do renderowania interfejsu użytkownika. Ten stan jest zwykle połączony z innymi warstwami ponieważ zawiera dane aplikacji. - Stan elementu interfejsu odnosi się do właściwości wchodzących w skład elementów interfejsu, które
wpływa na sposób ich renderowania. Element interfejsu może być wyświetlany lub ukryty i może
mają określoną czcionkę, jej rozmiar lub kolor. W widokach Androida
sam zarządza tym stanem, ponieważ jest z natury stanową i naraża metody
modyfikować jego stan ani wysyłać zapytania o jego stan. Na przykład
get
orazset
klasyTextView
dla jej tekstu. W plecaku odrzutowym tworzenia, stan jest spoza funkcji kompozycyjnej i możesz ją nawet podnieść w pobliżu funkcji kompozycyjnej, do funkcji lub reprezentatora stanu. Przykład:ScaffoldState
dla atrybutu Funkcja kompozycyjnaScaffold
.
Logiczna
Logiką aplikacji może być logika biznesowa lub logika interfejsu użytkownika:
- Logika biznesowa to implementacja wymagań dotyczących usług w przypadku aplikacji. i skalowalnych danych. Na przykład dodanie do zakładek artykułu w aplikacji czytnika wiadomości, gdy użytkownik klika przycisk. Działanie tej logiki zapisu zakładki w pliku lub bazie danych to umieszczone zwykle w domenach lub warstwach danych. Posiadacz stanu zazwyczaj przekazuje tę logikę do tych warstw, wywołując udostępniane przez nie metody.
- Logiki interfejsu dotyczą sposobu wyświetlania stanu UI na ekranie. Dla: np. gdy użytkownik wybrał kategoria, przewijanie do konkretnego elementu na liście lub logika nawigacji na określony ekran, gdy użytkownik kliknie przycisk.
Logika UI
Gdy logika interfejsu musi odczytywać stan lub zapisywać, ustaw zakres tego stanu na interfejs użytkownika zgodnie z jego cyklem życia. Aby to osiągnąć, w funkcji kompozycyjnej należy przenieść stan na odpowiedni poziom. Ewentualnie możesz za pomocą prostej klasy posiadacza stanu, która jest też ograniczona do cyklu życia interfejsu użytkownika.
Poniżej znajduje się opis obu rozwiązań i wyjaśnienie, kiedy należy z nich korzystać.
Elementy kompozycyjne jako właściciel stanu
Umieszczenie logiki interfejsu i stanu elementu interfejsu w elementach kompozycyjnych to dobre podejście, jeśli i logikę selekcjonowania danych. Stan swojego stanu możesz pozostawić wewnętrznym możesz użyć podnośnika.
Nie potrzeba podnoszenia stanu
Stan podnoszenia nie zawsze jest wymagany. Stan może być przechowywany wewnętrznie w funkcji kompozycyjnej gdy żadna inna funkcja kompozycyjna nie musi jej kontrolować. Ten fragment kodu zawiera funkcję kompozycyjną, która rozwija się i zwija po dotknięciu:
@Composable fun ChatBubble( message: Message ) { var showDetails by rememberSaveable { mutableStateOf(false) } // Define the UI element expanded state ClickableText( text = AnnotatedString(message.content), onClick = { showDetails = !showDetails } // Apply simple UI logic ) if (showDetails) { Text(message.timestamp) } }
Zmienna showDetails
jest stanem wewnętrznym tego elementu interfejsu. Jest tylko
odczytywane i modyfikowane w tej funkcji kompozycyjnej oraz stosowana do niej logika jest bardzo prosta.
Podniesienie państwa w tym przypadku nie przyniesie więc dużych korzyści,
ale może mieć charakter wewnętrzny. W ten sposób kompozytor stanie się właścicielem i singlem
jako źródło danych o stanie rozwiniętym.
Unoszące się w elementach kompozycyjnych
Jeśli musisz udostępnić stan elementu interfejsu innym komponentom kompozycyjnym i interfejsem stosowania w różnych miejscach, możesz umieścić ją wyżej w hierarchii UI. Dzięki temu elementy kompozycyjne lepiej nadaje się do wielokrotnego użytku i można je łatwiej testować.
Poniższy przykład przedstawia aplikację do obsługi czatu, która ma 2 funkcje:
- Przycisk
JumpToBottom
powoduje przewijanie listy wiadomości w dół. Przycisk wykonuje logikę interfejsu na liście. - Lista
MessagesList
przewija się w dół, gdy użytkownik wyśle nowe wiadomości. UserInput wykonuje logikę interfejsu użytkownika na podstawie stanu listy.
Hierarchia kompozycyjna wygląda tak:
.Stan LazyColumn
jest przenoszony do ekranu rozmowy, dzięki czemu aplikacja może
wykonywać logikę UI i odczytywać stan ze wszystkich elementów kompozycyjnych, które go wymagają:
Ostatecznie pliki kompozycyjne to:
Kod wygląda następująco:
@Composable private fun ConversationScreen(/*...*/) { val scope = rememberCoroutineScope() val lazyListState = rememberLazyListState() // State hoisted to the ConversationScreen MessagesList(messages, lazyListState) // Reuse same state in MessageList UserInput( onMessageSent = { // Apply UI logic to lazyListState scope.launch { lazyListState.scrollToItem(0) } }, ) } @Composable private fun MessagesList( messages: List<Message>, lazyListState: LazyListState = rememberLazyListState() // LazyListState has a default value ) { LazyColumn( state = lazyListState // Pass hoisted state to LazyColumn ) { items(messages, key = { message -> message.id }) { item -> Message(/*...*/) } } val scope = rememberCoroutineScope() JumpToBottom(onClicked = { scope.launch { lazyListState.scrollToItem(0) // UI logic being applied to lazyListState } }) }
Element LazyListState
jest podniesiony do poziomu wymaganego przez logikę UI, która musi być
zastosowano. Jest ona zainicjowana w funkcji kompozycyjnej, więc jest przechowywana w
Kompozycja według cyklu życia.
Zwróć uwagę, że lazyListState
jest zdefiniowany w metodzie MessagesList
, przy czym klucz
wartość domyślna to rememberLazyListState()
. Jest to typowy wzorzec w funkcji tworzenia wiadomości.
Dzięki temu elementy kompozycyjne stają się bardziej elastyczne i dają się wielokrotnego użytku. Możesz wtedy użyć funkcji kompozycyjnej,
w różnych częściach aplikacji, które nie muszą kontrolować stanu. To jest
co zwykle robi się podczas testowania lub wyświetlania podglądu elementu kompozycyjnego. Tak właśnie trzeba zrobić
LazyColumn
określa swój stan.
Zwykła klasa posiadacza stanu jako właściciel stanu
Gdy funkcja kompozycyjna zawiera złożoną logikę UI obejmującą jeden lub wiele stanów elementu interfejsu, powinien delegować ten obowiązek określania stanu , np. klasy zwykłej właściciela stanu. Dzięki temu logika funkcji kompozycyjnej łatwiejsze testowanie w oderwaniu od siebie i zmniejsza jego złożoność. Takie podejście faworyzuje zasada rozdziału potencjalnych problemów: za rząd odpowiada elementom kompozycyjnym emitujących elementy UI, a element stanu zawiera logikę UI .
Zwykłe klasy posiadaczy stanu zapewniają wygodne funkcje rozmówcom funkcję kompozycyjną, więc nie muszą sami pisać tej logiki.
Te zwykłe klasy są tworzone i zapamiętywane w kompozycji. Ponieważ
są zgodne z cyklem życia elementu kompozycyjnego, mogą przyjmować typy podane przez
Utwórz bibliotekę, np. rememberNavController()
lub rememberLazyListState()
.
Na przykład: LazyListState
klasa została zaimplementowana w Compose, aby kontrolować złożoność interfejsu LazyColumn
.
lub LazyRow
.
// LazyListState.kt @Stable class LazyListState constructor( firstVisibleItemIndex: Int = 0, firstVisibleItemScrollOffset: Int = 0 ) : ScrollableState { /** * The holder class for the current scroll position. */ private val scrollPosition = LazyListScrollPosition( firstVisibleItemIndex, firstVisibleItemScrollOffset ) suspend fun scrollToItem(/*...*/) { /*...*/ } override suspend fun scroll() { /*...*/ } suspend fun animateScrollToItem() { /*...*/ } }
LazyListState
zawiera stan instancji LazyColumn
, w której przechowywane są
scrollPosition
dla tego elementu interfejsu. Ujawnia również metody modyfikowania
pozycję przewijania (na przykład przewijanie do danego elementu).
Jak widać, zwiększenie zakresu odpowiedzialności elementu kompozycyjnego zwiększa niezbędne w przypadku państwa. Obowiązki mogą dotyczyć logiki interfejsu użytkownika ilość stanu, który należy śledzić.
Innym często spotykanym wzorcem jest użycie zwykłej klasy posiadacza stanu do obsługi złożoności funkcji kompozycyjnych głównych w aplikacji. Możesz użyć takiej klasy do uwzględnij stan na poziomie aplikacji, np. stan nawigacji i rozmiar ekranu. Kompletny tego opisu znajdziesz na stronie z elementami logicznymi interfejsu użytkownika i na stronie z opisem jego stanu.
Logika biznesowa
jeśli za logiką interfejsu użytkownika odpowiadają klasyom kompozycyjnym i klasy zwykłych właścicieli stanu, stan elementu interfejsu, właściciel stanu na poziomie ekranu odpowiada za: zadania:
- Zapewnienie dostępu do logiki biznesowej aplikacji, która które są zwykle umieszczane w innych warstwach hierarchii, takich jak firmy, warstw danych.
- Przygotowanie danych aplikacji do prezentacji na konkretnym ekranie, który staje się stanem UI ekranu.
ViewModels jako właściciel stanu
Zalety modeli AAC ViewModels w programowaniu na Androida sprawiają, że są one odpowiednie za zapewnienie dostępu do logiki biznesowej i przygotowanie danych aplikacji w celu prezentacji na ekranie.
Gdy podnosisz stan interfejsu w elemencie ViewModel
, przenosisz go poza
Kompozycja.
Modele ViewModel nie są przechowywane w ramach kompozycji. Są one dostarczane przez
i mają zakres ograniczony do ViewModelStoreOwner
, który może być
Aktywność, Fragment, wykres nawigacyjny lub miejsce docelowe wykresu nawigacyjnego. Dla:
więcej informacji o zakresach ViewModel
znajdziesz w dokumentacji.
ViewModel
jest źródłem prawdy i najniższym wspólnym elementem nadrzędnym dla
Stan interfejsu.
Stan interfejsu ekranu
Zgodnie z powyższymi definicjami stan interfejsu ekranu jest generowany przez zastosowanie wartości biznesowej.
reguł. Biorąc pod uwagę, że za to odpowiada właściciel stanu na poziomie ekranu,
oznacza, że stan interfejsu ekranu jest zwykle podnoszony na poziomie ekranu
właściciela, w tym przypadku ViewModel
.
Weź pod uwagę ConversationViewModel
aplikacji do obsługi czatu i sposób, w jaki prezentuje ona ekran
Stan interfejsu i zdarzenia do zmodyfikowania:
class ConversationViewModel( channelId: String, messagesRepository: MessagesRepository ) : ViewModel() { val messages = messagesRepository .getLatestMessages(channelId) .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5_000), initialValue = emptyList() ) // Business logic fun sendMessage(message: Message) { /* ... */ } }
Elementy kompozycyjne wykorzystują stan interfejsu ekranu zapisany w ViewModel
. Zalecenia
wstrzyknij instancję ViewModel
w elementach kompozycyjnych na poziomie ekranu, aby udostępnić
dostęp do logiki biznesowej.
Poniżej znajdziesz przykład elementu ViewModel
używanego w funkcji kompozycyjnej na poziomie ekranu.
W tym przypadku funkcja ConversationScreen()
kompozycyjna przetwarza dane o stanie UI ekranu
w ViewModel
:
@Composable private fun ConversationScreen( conversationViewModel: ConversationViewModel = viewModel() ) { val messages by conversationViewModel.messages.collectAsStateWithLifecycle() ConversationScreen( messages = messages, onSendMessage = { message: Message -> conversationViewModel.sendMessage(message) } ) } @Composable private fun ConversationScreen( messages: List<Message>, onSendMessage: (Message) -> Unit ) { MessagesList(messages, onSendMessage) /* ... */ }
Odwierty w budynkach
„Analiza usług” odnosi się do przekazywania danych przez kilka zagnieżdżonych elementów podrzędnych do miejsca, w którym są odczytywane.
Typowym przykładem możliwości odgłębiania właściwości w narzędziu Compose jest wstrzykiwanie reguły stanu na poziomie ekranu na najwyższym poziomie i przekazywania na urządzeniach kompozycyjnych dla dzieci. Może to też spowodować przeciążenie podpisy funkcji kompozycyjnych.
Mimo że ujawnienie zdarzeń jako poszczególnych parametrów lambda mogłoby przeciążyć podpisu funkcji, maksymalizuje to widoczność funkcji kompozycyjnej są obowiązki. Wystarczy rzut oka, by sprawdzić, do czego służy.
wędliny w usłudze są lepsze od tworzenia klas opakowań do hermetyzacji; stanu i zdarzeń w jednym miejscu, ponieważ zmniejsza to widoczność obowiązków kompozycyjnych. Poza tym nie masz klas kodu, przekazuje elementom kompozycyjnym tylko potrzebne parametry, co jest najlepszym ćwiczenie.
Ta sama sprawdzona metoda dotyczy zdarzeń związanych z nawigacją, dowiesz się więcej na ten temat z dokumentów nawigacyjnych.
Jeśli wykryjesz problem z wydajnością, możesz też odroczyć odczyt państwa. Więcej informacji znajdziesz w dokumentacji dotyczącej skuteczności.
Stan elementu interfejsu
Możesz przenieść stan elementu interfejsu do posiadacza stanu na poziomie ekranu, jeśli występuje to logika biznesowa, która musi ją odczytać lub zapisać.
Podobnie jak w przypadku aplikacji do obsługi czatu, aplikacja wyświetla sugestie użytkownika w
na czacie grupowym, gdy użytkownik wpisuje @
i podpowiedź. Sugestie te pochodzą z
warstwa danych i logika obliczania listy sugestii użytkowników są brane pod uwagę
to logika biznesowa. Funkcja wygląda tak:
Implementacja tej funkcji (ViewModel
) będzie wyglądać tak:
class ConversationViewModel(/*...*/) : ViewModel() { // Hoisted state var inputMessage by mutableStateOf("") private set val suggestions: StateFlow<List<Suggestion>> = snapshotFlow { inputMessage } .filter { hasSocialHandleHint(it) } .mapLatest { getHandle(it) } .mapLatest { repository.getSuggestions(it) } .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5_000), initialValue = emptyList() ) fun updateInput(newInput: String) { inputMessage = newInput } }
inputMessage
to zmienna przechowująca stan TextField
. Za każdym razem, gdy
użytkownik wpisuje nowe dane wejściowe, aplikacja wywołuje logikę biznesową, aby utworzyć suggestions
.
suggestions
to stan UI ekranu, który jest wykorzystywany w interfejsie tworzenia wiadomości przez
od StateFlow
.
Uwaga
W przypadku niektórych stanów elementu interfejsu tworzenia wiadomości przeniesienie do interfejsu ViewModel
może wymagać
ze szczególnymi względami. Na przykład niektóre posiadacze stanów elementów interfejsu Compose
i udostępniania metod
modyfikowania stanu. Niektóre z nich to funkcje zawieszania,
uruchamiać animacje. Te funkcje zawieszania mogą zgłaszać wyjątki, jeśli wywołasz
ich z CoroutineScope
, które nie jest ograniczone do
Kompozycja.
Załóżmy, że zawartość panelu aplikacji jest dynamiczna i musisz ją pobrać i odświeżyć.
z warstwy danych po jej zamknięciu. Należy podnieść stan panelu, aby
ViewModel
, aby można było wywołać w tym elemencie zarówno logikę UI, jak i logikę biznesową
od właściciela stanu.
Jednak wywołanie metody DrawerState
close()
za pomocą funkcji
viewModelScope
w interfejsie tworzenia wiadomości powoduje wyjątek typu środowiska wykonawczego
IllegalStateException
z komunikatem „a
Opcja MonotonicFrameClock
jest niedostępna w
CoroutineContext”
.
Aby rozwiązać ten problem, użyj właściwości CoroutineScope
o zakresie ograniczonym do kompozycji. Zapewnia
MonotonicFrameClock
w CoroutineContext
, która jest wymagana przez
zawieszać funkcje tak, aby działały.
Aby naprawić tę awarię, przełącz CoroutineContext
współprogramu w
ViewModel
do wartości, które są ograniczone do kompozycji. Może to wyglądać tak:
class ConversationViewModel(/*...*/) : ViewModel() { val drawerState = DrawerState(initialValue = DrawerValue.Closed) private val _drawerContent = MutableStateFlow(DrawerContent.Empty) val drawerContent: StateFlow<DrawerContent> = _drawerContent.asStateFlow() fun closeDrawer(uiScope: CoroutineScope) { viewModelScope.launch { withContext(uiScope.coroutineContext) { // Use instead of the default context drawerState.close() } // Fetch drawer content and update state _drawerContent.update { content } } } } // in Compose @Composable private fun ConversationScreen( conversationViewModel: ConversationViewModel = viewModel() ) { val scope = rememberCoroutineScope() ConversationScreen(onCloseDrawer = { conversationViewModel.closeDrawer(uiScope = scope) }) }
Więcej informacji
Więcej informacji o stanie i Jetpack Compose znajdziesz w tych artykułach: z dodatkowymi zasobami.
Próbki
Ćwiczenia z programowania
Filmy
.Polecane dla Ciebie
- Uwaga: tekst linku wyświetla się, gdy JavaScript jest wyłączony
- Zapisywanie stanu interfejsu w narzędziu Compose
- Listy i siatki
- Tworzenie architektury interfejsu tworzenia wiadomości