Wenn Sie mit Seiten aus verwenden, müssen Sie oft und transformieren den Datenstream beim Laden. Beispielsweise müssen Sie eventuell eine Liste mit Elementen erstellen oder Elemente vor der Präsentation in einen anderen Typ konvertieren auf der Benutzeroberfläche. Ein weiterer häufiger Anwendungsfall für die Umwandlung von Datenstreams ist das Hinzufügen einer Liste Trennzeichen.
Im Allgemeinen können Sie durch direkte Anwendung von Transformationen auf den Datenstream um Ihre Repository-Konstrukte und UI-Konstrukte voneinander zu trennen.
Auf dieser Seite wird davon ausgegangen, dass Sie mit den grundlegenden Funktionen der Paging-Funktion vertraut sind. Bibliothek.
Grundlegende Transformationen anwenden
Weil PagingData
gekapselt in einem reaktiven Stream können Sie Transformationsvorgänge auf den
zwischen dem Laden und
Präsentieren der Daten stufenweise.
Um Transformationen auf jedes PagingData
-Objekt im Stream anzuwenden,
die Transformationen in eine
map()
Aktion im Stream:
Kotlin
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. }
Java
PagingRx.getFlowable(pager) // Type is Flowable<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. });
Java
// Map the outer stream so that the transformations are applied to // each new generation of PagingData. Transformations.map( // Type is LiveData<PagingData<User>>. PagingLiveData.getLiveData(pager), pagingData -> { // Transformations in this block are applied to the items // in the paged data. });
Daten konvertieren
Der einfachste Vorgang bei einem Datenstrom besteht darin, ihn in einen anderen
Typ. Sobald du Zugriff auf das PagingData
-Objekt hast, kannst du einen map()
-Vorgang ausführen
Vorgang für jedes einzelne Element in der Seitenliste innerhalb von PagingData
-Objekt enthält.
Ein häufiger Anwendungsfall hierfür ist die Zuordnung eines Netzwerk- oder Datenbankschichtobjekts zu ein -Objekt, das speziell in der UI-Ebene verwendet wird. Das folgende Beispiel zeigt, wie um diesen Kartenvorgang anzuwenden:
Kotlin
pager.flow // Type is Flow<PagingData<User>>. .map { pagingData -> pagingData.map { user -> UiModel(user) } }
Java
// Type is Flowable<PagingData<User>>. PagingRx.getFlowable(pager) .map(pagingData -> pagingData.map(UiModel.UserModel::new) )
Java
Transformations.map( // Type is LiveData<PagingData<User>>. PagingLiveData.getLiveData(pager), pagingData -> pagingData.map(UiModel.UserModel::new) )
Eine weitere gängige Daten-Conversion ist die Erfassung einer Eingabe des Nutzers, z. B. einer Abfrage String und wandeln ihn in die anzuzeigende Anfrageausgabe um. Einrichtung auf die Eingabe der Suchanfrage des Nutzers warten und erfassen, und das Abfrageergebnisse an die UI zurückübertragen.
Sie können die Abfrageeingabe mit einer Stream-API überwachen. Streamreferenz beibehalten
in ViewModel
. Die UI-Ebene sollte keinen direkten Zugriff darauf haben. verwenden Sie stattdessen
Definieren Sie eine Funktion, um ViewModel über die Anfrage des Nutzers zu informieren.
Kotlin
private val queryFlow = MutableStateFlow("") fun onQueryChanged(query: String) { queryFlow.value = query }
Java
private BehaviorSubject<String> querySubject = BehaviorSubject.create(""); public void onQueryChanged(String query) { queryFlow.onNext(query) }
Java
private MutableLiveData<String> queryLiveData = new MutableLiveData(""); public void onQueryChanged(String query) { queryFlow.setValue(query) }
Wenn sich der Abfragewert im Datenstream ändert, können Sie Vorgänge ausführen, um Den Abfragewert in den gewünschten Datentyp konvertieren und das Ergebnis an die Benutzeroberfläche zurückgeben Ebene. Die spezifische Konvertierungsfunktion hängt von der Sprache und dem Framework ab. aber alle bieten ähnliche Funktionen.
Kotlin
val querySearchResults = queryFlow.flatMapLatest { query -> // The database query returns a Flow which is output through // querySearchResults userDatabase.searchBy(query) }
Java
Observable<User> querySearchResults = querySubject.switchMap(query -> userDatabase.searchBy(query));
Java
LiveData<User> querySearchResults = Transformations.switchMap( queryLiveData, query -> userDatabase.searchBy(query) );
Mit Vorgängen wie flatMapLatest
oder switchMap
wird sichergestellt, dass nur die
werden die neuesten Ergebnisse an die Benutzeroberfläche zurückgegeben. Wenn der Nutzer seine Abfrageeingabe ändert
bevor der Datenbankvorgang abgeschlossen ist, werden die Ergebnisse
aus der alten Abfrage und starten Sie die neue sofort.
Daten filtern
Ein weiterer gängiger Vorgang ist das Filtern. Sie können Daten nach Kriterien filtern, oder Daten aus der Benutzeroberfläche entfernen, falls sie ausgeblendet werden sollen, zu anderen Kriterien.
Sie müssen diese Filtervorgänge innerhalb des map()
-Aufrufs platzieren, da die
Der Filter gilt für das PagingData
-Objekt. Sobald die Daten aus den
PagingData
, die neue PagingData
-Instanz wird an die UI-Ebene an
Display.
Kotlin
pager.flow // Type is Flow<PagingData<User>>. .map { pagingData -> pagingData.filter { user -> !user.hiddenFromUi } }
Java
// Type is Flowable<PagingData<User>>. PagingRx.getFlowable(pager) .map(pagingData -> pagingData.filter(user -> !user.isHiddenFromUi()) ) }
Java
Transformations.map( // Type is LiveData<PagingData<User>>. PagingLiveData.getLiveData(pager), pagingData -> pagingData.filter(user -> !user.isHiddenFromUi()) )
Listentrennzeichen hinzufügen
Die Paging-Bibliothek unterstützt dynamische Listentrennzeichen. Sie können die Liste
durch Einfügen von Trennzeichen direkt in den Datenstream
RecyclerView
Listeneinträge. Daher sind Trennzeichen vollständig verfügbar.
ViewHolder
-Objekten erstellen und so Interaktivität, Bedienungshilfen und
die anderen von View
bereitgestellten Funktionen.
Zum Einfügen von Trennzeichen in die Liste mit Seiten sind drei Schritte erforderlich:
- Konvertieren Sie das UI-Modell, um die Trennzeichenelemente aufzunehmen.
- Datenstream transformieren, sodass die Trennzeichen zwischen den Ladevorgängen und Präsentation der Daten.
- Aktualisieren Sie die Benutzeroberfläche, um Trennzeichenelemente zu verarbeiten.
UI-Modell konvertieren
Die Paging-Bibliothek fügt Listentrennzeichen als tatsächliches Element in RecyclerView
ein.
Listenelemente, wobei die Trennzeichen von den Datenelementen unterscheidbar sein müssen.
in der Liste aus, damit sie sich an einen anderen ViewHolder
-Typ mit einem
auf der Benutzeroberfläche. Die Lösung ist, ein versiegeltes
Kurs
mit Unterklassen zur Darstellung Ihrer Daten und Ihrer Trennzeichen. Alternativ können Sie
können Sie eine Basisklasse erstellen, die durch Ihre Listenelementklasse und Ihren
Trennzeichenklasse.
Angenommen, Sie möchten einer Liste mit User
-Elementen auf Seitenebene Trennzeichen hinzufügen. Die
Das folgende Snippet zeigt, wie eine Basisklasse erstellt wird, bei der die Instanzen
entweder UserModel
oder SeparatorModel
:
Kotlin
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() }
Java
class UiModel { private UiModel() {} static class UserModel extends UiModel { @NonNull private String mId; @NonNull private String mLabel; UserModel(@NonNull String id, @NonNull String label) { mId = id; mLabel = label; } UserModel(@NonNull User user) { mId = user.id; mLabel = user.label; } @NonNull public String getId() { return mId; } @NonNull public String getLabel() { return mLabel; } } static class SeparatorModel extends UiModel { @NonNull private String mDescription; SeparatorModel(@NonNull String description) { mDescription = description; } @NonNull public String getDescription() { return mDescription; } } }
Java
class UiModel { private UiModel() {} static class UserModel extends UiModel { @NonNull private String mId; @NonNull private String mLabel; UserModel(@NonNull String id, @NonNull String label) { mId = id; mLabel = label; } UserModel(@NonNull User user) { mId = user.id; mLabel = user.label; } @NonNull public String getId() { return mId; } @NonNull public String getLabel() { return mLabel; } } static class SeparatorModel extends UiModel { @NonNull private String mDescription; SeparatorModel(@NonNull String description) { mDescription = description; } @NonNull public String getDescription() { return mDescription; } } }
Datenstream transformieren
Sie müssen Transformationen auf den Datenstream anwenden, nachdem er und bevor er geladen wurde präsentieren. Die Transformationen sollten folgende Schritte ausführen:
- Konvertieren Sie die geladenen Listenelemente, sodass sie dem neuen Basiselementtyp entsprechen.
- Verwenden Sie die Methode
PagingData.insertSeparators()
, um die Trennzeichen hinzuzufügen.
Weitere Informationen zu Transformationsvorgängen finden Sie unter Grundlegende Schritte für Transformationen.
Das folgende Beispiel zeigt Transformationsvorgänge zum Aktualisieren der
PagingData<User>
-Stream mit Trennzeichen in einen PagingData<UiModel>
-Stream
hinzugefügt:
Kotlin
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 } } }
Java
// Map outer stream, so you can perform transformations on each // paging generation. PagingRx.getFlowable(pager).map(pagingData -> { // First convert items in stream to UiModel.UserModel. PagingData<UiModel> uiModelPagingData = pagingData.map( UiModel.UserModel::new); // Insert UiModel.SeparatorModel, which produces PagingData of // generic type UiModel. return PagingData.insertSeparators(uiModelPagingData, (@Nullable UiModel before, @Nullable UiModel after) -> { if (before == null) { return new UiModel.SeparatorModel("HEADER"); } else if (after == null) { return new UiModel.SeparatorModel("FOOTER"); } else if (shouldSeparate(before, after)) { return new UiModel.SeparatorModel("BETWEEN ITEMS " + before.toString() + " AND " + after.toString()); } else { // Return null to avoid adding a separator between two // items. return null; } }); });
Java
// Map outer stream, so you can perform transformations on each // paging generation. Transformations.map(PagingLiveData.getLiveData(pager), pagingData -> { // First convert items in stream to UiModel.UserModel. PagingData<UiModel> uiModelPagingData = pagingData.map( UiModel.UserModel::new); // Insert UiModel.SeparatorModel, which produces PagingData of // generic type UiModel. return PagingData.insertSeparators(uiModelPagingData, (@Nullable UiModel before, @Nullable UiModel after) -> { if (before == null) { return new UiModel.SeparatorModel("HEADER"); } else if (after == null) { return new UiModel.SeparatorModel("FOOTER"); } else if (shouldSeparate(before, after)) { return new UiModel.SeparatorModel("BETWEEN ITEMS " + before.toString() + " AND " + after.toString()); } else { // Return null to avoid adding a separator between two // items. return null; } }); });
Trennzeichen in der Benutzeroberfläche verarbeiten
Der letzte Schritt besteht darin, Ihre Benutzeroberfläche so zu ändern, dass sie den Elementtyp „Trennzeichen“ enthält.
Erstellen Sie ein Layout und einen Ansichtshalter für die Trennzeichenelemente und ändern Sie die Liste
Adapter so, dass RecyclerView.ViewHolder
als Halterungstyp verwendet wird,
mehrere Typen von Ansichtsinhabern verarbeiten kann. Alternativ können Sie eine gemeinsame
Basisklasse , die sowohl die Halteklasse für die Ansicht "item" als auch die der Separator-Ansicht erweitert.
Nehmen Sie außerdem die folgenden Änderungen am Listenadapter vor:
- Fügen Sie den Methoden
onCreateViewHolder()
undonBindViewHolder()
Fälle hinzu, um Trennzeichenlistenelemente berücksichtigt. - Implementieren Sie einen neuen Vergleichsoperator.
Kotlin
class UiModelAdapter : PagingDataAdapter<UiModel, RecyclerView.ViewHolder>(UiModelComparator) { override fun onCreateViewHolder( parent: ViewGroup, viewType: Int ) = when (viewType) { R.layout.item -> UserModelViewHolder(parent) else -> SeparatorModelViewHolder(parent) } override fun getItemViewType(position: Int) { // Use peek over getItem to avoid triggering page fetch / drops, since // recycling views is not indicative of the user's current scroll position. return when (peek(position)) { is UiModel.UserModel -> R.layout.item is UiModel.SeparatorModel -> R.layout.separator_item null -> throw IllegalStateException("Unknown view") } } override fun onBindViewHolder( holder: RecyclerView.ViewHolder, position: Int ) { val item = getItem(position) if (holder is UserModelViewHolder) { holder.bind(item as UserModel) } else if (holder is SeparatorModelViewHolder) { holder.bind(item as SeparatorModel) } } } object UiModelComparator : DiffUtil.ItemCallback<UiModel>() { override fun areItemsTheSame( oldItem: UiModel, newItem: UiModel ): Boolean { val isSameRepoItem = oldItem is UiModel.UserModel && newItem is UiModel.UserModel && oldItem.id == newItem.id val isSameSeparatorItem = oldItem is UiModel.SeparatorModel && newItem is UiModel.SeparatorModel && oldItem.description == newItem.description return isSameRepoItem || isSameSeparatorItem } override fun areContentsTheSame( oldItem: UiModel, newItem: UiModel ) = oldItem == newItem }
Java
class UiModelAdapter extends PagingDataAdapter<UiModel, RecyclerView.ViewHolder> { UiModelAdapter() { super(new UiModelComparator(), Dispatchers.getMain(), Dispatchers.getDefault()); } @NonNull @Override public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { if (viewType == R.layout.item) { return new UserModelViewHolder(parent); } else { return new SeparatorModelViewHolder(parent); } } @Override public int getItemViewType(int position) { // Use peek over getItem to avoid triggering page fetch / drops, since // recycling views is not indicative of the user's current scroll position. UiModel item = peek(position); if (item instanceof UiModel.UserModel) { return R.layout.item; } else if (item instanceof UiModel.SeparatorModel) { return R.layout.separator_item; } else { throw new IllegalStateException("Unknown view"); } } @Override public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { if (holder instanceOf UserModelViewHolder) { UserModel userModel = (UserModel) getItem(position); ((UserModelViewHolder) holder).bind(userModel); } else { SeparatorModel separatorModel = (SeparatorModel) getItem(position); ((SeparatorModelViewHolder) holder).bind(separatorModel); } } } class UiModelComparator extends DiffUtil.ItemCallback<UiModel> { @Override public boolean areItemsTheSame(@NonNull UiModel oldItem, @NonNull UiModel newItem) { boolean isSameRepoItem = oldItem instanceof UserModel && newItem instanceof UserModel && ((UserModel) oldItem).getId().equals(((UserModel) newItem).getId()); boolean isSameSeparatorItem = oldItem instanceof SeparatorModel && newItem instanceof SeparatorModel && ((SeparatorModel) oldItem).getDescription().equals( ((SeparatorModel) newItem).getDescription()); return isSameRepoItem || isSameSeparatorItem; } @Override public boolean areContentsTheSame(@NonNull UiModel oldItem, @NonNull UiModel newItem) { return oldItem.equals(newItem); } }
Java
class UiModelAdapter extends PagingDataAdapter<UiModel, RecyclerView.ViewHolder> { UiModelAdapter() { super(new UiModelComparator(), Dispatchers.getMain(), Dispatchers.getDefault()); } @NonNull @Override public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { if (viewType == R.layout.item) { return new UserModelViewHolder(parent); } else { return new SeparatorModelViewHolder(parent); } } @Override public int getItemViewType(int position) { // Use peek over getItem to avoid triggering page fetch / drops, since // recycling views is not indicative of the user's current scroll position. UiModel item = peek(position); if (item instanceof UiModel.UserModel) { return R.layout.item; } else if (item instanceof UiModel.SeparatorModel) { return R.layout.separator_item; } else { throw new IllegalStateException("Unknown view"); } } @Override public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { if (holder instanceOf UserModelViewHolder) { UserModel userModel = (UserModel) getItem(position); ((UserModelViewHolder) holder).bind(userModel); } else { SeparatorModel separatorModel = (SeparatorModel) getItem(position); ((SeparatorModelViewHolder) holder).bind(separatorModel); } } } class UiModelComparator extends DiffUtil.ItemCallback<UiModel> { @Override public boolean areItemsTheSame(@NonNull UiModel oldItem, @NonNull UiModel newItem) { boolean isSameRepoItem = oldItem instanceof UserModel && newItem instanceof UserModel && ((UserModel) oldItem).getId().equals(((UserModel) newItem).getId()); boolean isSameSeparatorItem = oldItem instanceof SeparatorModel && newItem instanceof SeparatorModel && ((SeparatorModel) oldItem).getDescription().equals( ((SeparatorModel) newItem).getDescription()); return isSameRepoItem || isSameSeparatorItem; } @Override public boolean areContentsTheSame(@NonNull UiModel oldItem, @NonNull UiModel newItem) { return oldItem.equals(newItem); } }
Doppelte Aufgaben vermeiden
Ein wichtiges Problem, das Sie vermeiden sollten, besteht darin, dass die App unnötige Funktionen ausführt. Datenabruf ist und Datentransformationen können ebenfalls wertvolle Zeit in Anspruch nehmen. Sobald die Daten geladen und für die Anzeige in der Benutzeroberfläche vorbereitet wurden, sollten sie gespeichert werden. falls eine Konfigurationsänderung erfolgt und die Benutzeroberfläche neu erstellt werden muss.
Der Vorgang cachedIn()
speichert die Ergebnisse aller auftretenden Transformationen im Cache
davor. Daher sollte cachedIn()
der letzte Aufruf in ViewModel sein.
Kotlin
pager.flow // Type is Flow<PagingData<User>>. .map { pagingData -> pagingData.filter { user -> !user.hiddenFromUi } .map { user -> UiModel.UserModel(user) } } .cachedIn(viewModelScope)
Java
// CoroutineScope helper provided by the lifecycle-viewmodel-ktx artifact. CoroutineScope viewModelScope = ViewModelKt.getViewModelScope(viewModel); PagingRx.cachedIn( // Type is Flowable<PagingData<User>>. PagingRx.getFlowable(pager) .map(pagingData -> pagingData .filter(user -> !user.isHiddenFromUi()) .map(UiModel.UserModel::new)), viewModelScope); }
Java
// CoroutineScope helper provided by the lifecycle-viewmodel-ktx artifact. CoroutineScope viewModelScope = ViewModelKt.getViewModelScope(viewModel); PagingLiveData.cachedIn( Transformations.map( // Type is LiveData<PagingData<User>>. PagingLiveData.getLiveData(pager), pagingData -> pagingData .filter(user -> !user.isHiddenFromUi()) .map(UiModel.UserModel::new)), viewModelScope);
Weitere Informationen zur Verwendung von cachedIn()
mit einem Stream von PagingData
finden Sie unter
Richten Sie einen Stream mit
PagingData
Weitere Informationen
Weitere Informationen zur Paging-Bibliothek finden Sie in den folgenden zusätzlichen Ressourcen:
Codelabs
Produktproben
- Seitenumbruch für Android-Architekturkomponenten Beispiel
- Seitenwechsel mit Datenbank und Netzwerk in Android-Architekturkomponenten Beispiel
Empfehlungen für dich
- Hinweis: Der Linktext wird angezeigt, wenn JavaScript deaktiviert ist.
- Daten aus Seiten laden und anzeigen
- Paging-Implementierung testen
- Ladestatus verwalten und anzeigen