Podczas pracy ze stronnicowanymi danymi często trzeba przekształcać strumień danych podczas jego wczytywania. Może być na przykład konieczne odfiltrowanie listy elementów lub przekonwertowanie ich na inny typ przed wyświetleniem w interfejsie. Innym typowym zastosowaniem przekształcania strumienia danych jest dodawanie separatorów list.
Ogólnie rzecz biorąc, stosowanie przekształceń bezpośrednio w strumieniu danych pozwala zachować oddzielenie konstrukcji repozytorium i konstrukcji interfejsu.
Na tej stronie zakładamy, że znasz podstawowe zastosowania biblioteki Paging.
Stosowanie podstawowych przekształceń
Ponieważ PagingData jest zamknięty w strumieniu reaktywnym, możesz stopniowo stosować operacje przekształcania danych między ich wczytaniem a wyświetleniem.
Aby zastosować przekształcenia do każdego obiektu PagingData w strumieniu, umieść przekształcenia w operacji map() na strumieniu:
pager.flow // Type is Flow<PagingData<User>>. // Map the outer stream so that the transformations are applied to // each new generation of PagingData. .map { pagingData -> // Transformations in this block are applied to the items // in the paged data. }
Konwertowanie danych
Najbardziej podstawową operacją na strumieniu danych jest przekształcenie go w inny typ. Gdy uzyskasz dostęp do obiektu PagingData, możesz wykonać operację map() na każdym poszczególnym elemencie na stronie listy w obiekcie PagingData.
Jednym z typowych zastosowań jest mapowanie obiektu warstwy sieci lub bazy danych na obiekt używany w warstwie interfejsu. Poniższy przykład pokazuje, jak zastosować ten typ operacji mapowania:
pager.flow // Type is Flow<PagingData<User>>. .map { pagingData -> pagingData.map { user -> UiModel(user) } }
Innym typowym przykładem konwersji danych jest pobieranie danych wejściowych od użytkownika, np. ciągu zapytania, i przekształcanie ich w dane wyjściowe żądania, które mają być wyświetlane. Skonfigurowanie tego wymaga nasłuchiwania i przechwytywania danych wejściowych zapytania użytkownika, wykonania żądania i przesłania wyniku zapytania z powrotem do interfejsu.
Możesz nasłuchiwać danych wejściowych zapytania za pomocą interfejsu API strumienia. Zachowaj odniesienie do strumienia w ViewModel. Warstwa interfejsu nie powinna mieć do niej bezpośredniego dostępu. Zamiast tego zdefiniuj funkcję, która powiadomi ViewModel o zapytaniu użytkownika.
private val queryFlow = MutableStateFlow("") fun onQueryChanged(query: String) { queryFlow.value = query }
Gdy wartość zapytania w strumieniu danych ulegnie zmianie, możesz wykonać operacje, aby przekonwertować ją na odpowiedni typ danych i zwrócić wynik do warstwy interfejsu. Konkretna funkcja konwersji zależy od używanego języka i platformy, ale wszystkie zapewniają podobną funkcjonalność.
val querySearchResults: Flow<User> = queryFlow.flatMapLatest { query -> // The database query returns a Flow which is output through // querySearchResults userDatabase.searchBy(query) }
Użycie operacji takich jak flatMapLatest lub switchMap gwarantuje, że do interfejsu zostaną zwrócone tylko najnowsze wyniki. Jeśli użytkownik zmieni dane wejściowe zapytania przed zakończeniem operacji na bazie danych, te operacje odrzucą wyniki starego zapytania i natychmiast uruchomią nowe wyszukiwanie.
Filtrowanie danych
Inną powszechną operacją jest filtrowanie. Dane możesz filtrować na podstawie kryteriów podanych przez użytkownika lub usuwać je z interfejsu, jeśli powinny być ukryte na podstawie innych kryteriów.
Operacje filtrowania musisz umieścić w wywołaniu map(), ponieważ filtr jest stosowany do obiektu PagingData. Po odfiltrowaniu danych z PagingData nowa instancja PagingData jest przekazywana do warstwy interfejsu, aby ją wyświetlić.
pager.flow // Type is Flow<PagingData<User>>. .map { pagingData -> pagingData.filter { user -> !user.hiddenFromUi } }
Dodawanie separatorów list
Biblioteka Paging obsługuje dynamiczne separatory list. Możesz zwiększyć czytelność listy, wstawiając separatory bezpośrednio do strumienia danych jako elementy kompozycyjne w układzie. Dzięki temu separatory są w pełni funkcjonalnymi komponentami kompozycyjnymi, które umożliwiają pełną interaktywność, stylizację i semantykę ułatwień dostępu.
Aby wstawić separatory na listę podzieloną na strony, wykonaj te 3 czynności:
- Przekształć model interfejsu, aby uwzględnić elementy separatora. Jednym ze sposobów na to jest umieszczenie elementu danych i separatora w jednej klasie zamkniętej. Dzięki temu interfejs może obsługiwać wiele typów elementów na tej samej liście.
- Przekształć strumień danych, aby dynamicznie dodawać separatory między wczytywaniem danych a ich prezentowaniem.
- Zaktualizuj interfejs, aby obsługiwał elementy separatora.
Konwertowanie modelu interfejsu
Biblioteka Paging wstawia separatory list do interfejsu jako rzeczywiste elementy listy, ale elementy separatora muszą być odróżnialne od elementów danych na liście, aby oba typy kompozycyjne były renderowane w odmienny sposób. Rozwiązaniem jest utworzenie klasy zapieczętowanej w języku Kotlin z podklasami reprezentującymi dane i separatory. Możesz też utworzyć klasę bazową, która będzie rozszerzana przez klasę elementu listy i klasę separatora.
Załóżmy, że chcesz dodać separatory do stronicowanej listy User elementów. Poniższy fragment kodu pokazuje, jak utworzyć klasę bazową, której instancje mogą być typu UserModel lub SeparatorModel:
sealed class UiModel { class UserModel(val id: String, val label: String) : UiModel() { constructor(user: User) : this(user.id, user.label) } class SeparatorModel(val description: String) : UiModel() }
Przekształcanie strumienia danych
Przekształcenia musisz zastosować do strumienia danych po jego wczytaniu, a przed jego wyświetleniem. Przekształcenia powinny wykonywać te czynności:
- Przekształć załadowane elementy listy, aby odzwierciedlały nowy typ produktu podstawowego.
- Aby dodać separatory, użyj metody
PagingData.insertSeparators().
Więcej informacji o operacjach przekształcania znajdziesz w artykule Stosowanie podstawowych przekształceń.
W przykładzie poniżej pokazujemy operacje przekształcania, które aktualizują strumień PagingData<User> do strumienia PagingData<UiModel> z dodanymi separatorami:
pager.flow.map { pagingData: PagingData<User> -> // Map outer stream, so you can perform transformations on // each paging generation. pagingData .map { user -> // Convert items in stream to UiModel.UserModel. UiModel.UserModel(user) } .insertSeparators<UiModel.UserModel, UiModel> { before, after -> when { before == null -> UiModel.SeparatorModel("HEADER") after == null -> UiModel.SeparatorModel("FOOTER") shouldSeparate(before, after) -> UiModel.SeparatorModel( "BETWEEN ITEMS $before AND $after" ) // Return null to avoid adding a separator between two items. else -> null } } }
Obsługa separatorów w interfejsie
Ostatnim krokiem jest zmiana interfejsu, aby uwzględnić typ produktu separatora.
W układzie leniwym możesz obsługiwać wiele typów elementów, sprawdzając typ każdego wyemitowanego elementu UiModel. Podczas iteracji po danych podzielonych na strony użyj instrukcji when, aby wywołać odpowiedni komponent. Dzięki temu możesz zapewnić odrębny interfejs danych i separatorów.
@Composable fun UserList(pagingItems: LazyPagingItems) { LazyColumn { items( count = pagingItems.itemCount, key = { index -> val item = pagingItems.peek(index) when (item) { is UiModel.UserModel -> item.user.id is UiModel.SeparatorModel -> item.description else -> index } } ) { index -> when (val item = pagingItems[index]) { is UiModel.UserModel -> UserItemComposable(item.user) is UiModel.SeparatorModel -> SeparatorComposable(item.description) null -> PlaceholderComposable() } } } }
Unikanie powielania pracy
Jednym z głównych problemów, których należy unikać, jest wykonywanie przez aplikację niepotrzebnej pracy. Pobieranie danych jest kosztowną operacją, a przekształcanie danych może również zająć dużo czasu. Po załadowaniu danych i przygotowaniu ich do wyświetlenia w interfejsie należy je zapisać na wypadek zmiany konfiguracji i konieczności ponownego utworzenia interfejsu.
Operacja cachedIn() buforuje wyniki wszystkich przekształceń, które występują przed nią. Zazwyczaj stosujesz ten operator w ViewModel przed udostępnieniem Flow w funkcjach kompozycyjnych.
Aby prawidłowo zarządzać pamięcią podręczną, przekaż CoroutineScope do cachedIn(), jak pokazano w tym przykładzie z użyciem viewModelScope.
pager.flow // Type is Flow<PagingData<User>>. .map { pagingData -> pagingData.filter { user -> !user.hiddenFromUi } .map { user -> UiModel.UserModel(user) } } .cachedIn(viewModelScope)
Więcej informacji o używaniu cachedIn() ze strumieniem PagingData znajdziesz w artykule Konfigurowanie strumienia PagingData.
Dodatkowe materiały
Więcej informacji o bibliotece Paging znajdziesz w tych materiałach:
Dokumentacja
Wyświetlanie treści
Polecane dla Ciebie
- Uwaga: tekst linku jest wyświetlany, gdy język JavaScript jest wyłączony.
- Wczytywanie i wyświetlanie danych podzielonych na strony
- Testowanie implementacji biblioteki Paging
- Zarządzanie stanami wczytywania i ich prezentowanie