Datenstreams umwandeln

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:

  1. Konvertieren Sie das UI-Modell, um die Trennzeichenelemente aufzunehmen.
  2. Datenstream transformieren, sodass die Trennzeichen zwischen den Ladevorgängen und Präsentation der Daten.
  3. 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() und onBindViewHolder() 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