Veri akışlarını dönüştürme

Sayfalandırılmış verilerle çalışırken genellikle veri akışını yüklerken dönüştürmeniz gerekir. Örneğin, bir öğe listesini filtrelemeniz veya öğeleri kullanıcı arayüzünde sunmadan önce farklı bir türe dönüştürmeniz gerekebilir. Veri akışı dönüşümü için bir diğer yaygın kullanım alanı liste ayırıcılar eklemedir.

Daha genel olarak, dönüşümleri doğrudan veri akışına uygulamak, depo yapılarınızı ve kullanıcı arayüzü yapılarınızı ayrı tutmanıza olanak tanır.

Bu sayfada, Sayfalama kitaplığının temel kullanımı hakkında bilgi sahibi olduğunuz varsayılır.

Temel dönüşümleri uygulama

PagingData reaktif bir akışa dahil edildiği için veri yükleme ve sunma arasında, veriler üzerinde dönüşüm işlemlerini aşamalı olarak uygulayabilirsiniz.

Akıştaki her PagingData nesnesine dönüşüm uygulamak için dönüşümleri akıştaki bir map() işleminin içine yerleştirin:

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.
  });

Verileri dönüştürün

Bir veri akışındaki en temel işlem, veri akışının farklı bir türe dönüştürülmesidir. PagingData nesnesine erişim elde ettikten sonra PagingData nesnesindeki sayfalandırılmış listedeki her bir öğe için bir map() işlemi gerçekleştirebilirsiniz.

Bunun yaygın bir kullanım alanı, bir ağ veya veritabanı katmanı nesnesini, kullanıcı arayüzü katmanında özel olarak kullanılan bir nesneyle eşlemektir. Aşağıdaki örnekte, bu tür bir harita işleminin nasıl uygulanacağı gösterilmektedir:

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)
)

Yaygın olarak kullanılan bir diğer veri dönüşümü, kullanıcıdan sorgu dizesi gibi bir giriş almak ve bunu görüntülenecek istek çıkışına dönüştürmektir. Bunu ayarlamak için kullanıcının sorgu girişini dinlemeyi ve yakalamayı, isteği gerçekleştirmeyi ve sorgu sonucunu tekrar kullanıcı arayüzüne aktarmayı gerektirir.

Bir akış API'si kullanarak sorgu girişini dinleyebilirsiniz. Akış referansını ViewModel içinde tutun. Kullanıcı arayüzü katmanının buna doğrudan erişimi olmamalıdır. Bunun yerine, ViewModel'i kullanıcının sorgusunu bilgilendirecek bir işlev tanımlayın.

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)
}

Veri akışında sorgu değeri değiştiğinde, sorgu değerini istediğiniz veri türüne dönüştürmek ve sonucu kullanıcı arayüzü katmanına döndürmek için işlemler gerçekleştirebilirsiniz. Dönüşüm işlevi, kullanılan dile ve çerçeveye göre değişir, ancak hepsi benzer işlevler sunar.

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)
);

flatMapLatest veya switchMap gibi işlemlerin kullanılması, kullanıcı arayüzüne yalnızca en son sonuçların döndürülmesini sağlar. Kullanıcı, veritabanı işlemi tamamlanmadan önce sorgu girişini değiştirirse bu işlemler eski sorgudaki sonuçları siler ve yeni aramayı hemen başlatır.

Verileri filtreleme

Diğer bir yaygın işlem de filtrelemedir. Verileri kullanıcının ölçütlerine göre filtreleyebilir veya diğer ölçütlere göre gizlenmesi gereken verileri kullanıcı arayüzünden kaldırabilirsiniz.

Filtre PagingData nesnesine uygulandığından bu filtre işlemlerini map() çağrısının içine yerleştirmeniz gerekir. Veriler filtrelenerek PagingData dışına çıkarıldığında yeni PagingData örneği, görüntülenmek üzere kullanıcı arayüzü katmanına iletilir.

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())
)

Liste ayırıcıları ekle

Sayfalama kitaplığı dinamik liste ayırıcılarını destekler. RecyclerView liste öğeleri olarak ayırıcıları doğrudan veri akışına ekleyerek liste okunabilirliğini artırabilirsiniz. Sonuç olarak, ayırıcılar tam özellikli ViewHolder nesneleridir ve etkileşimi, erişilebilirlik odağını ve View tarafından sağlanan diğer tüm özellikleri etkinleştirir.

Sayfalı listenize ayırıcı ekleme işlemi üç adımdan oluşur:

  1. Kullanıcı arayüzü modelini, ayırıcı öğelere yer verecek şekilde dönüştürün.
  2. Veri akışı ile verileri sunma arasındaki ayırıcıları dinamik olarak eklemek için dönüştürün.
  3. Kullanıcı arayüzünü, ayırıcı öğeleri işleyecek şekilde güncelleyin.

Kullanıcı arayüzü modelini dönüştürün

Sayfalama kitaplığı, liste ayırıcılarını RecyclerView içine gerçek liste öğeleri olarak ekler. Ancak ayırıcı öğelerin, farklı bir kullanıcı arayüzüyle farklı bir ViewHolder türüne bağlanabilmeleri için listedeki veri öğelerinden ayırt edilebilmeleri gerekir. Bu sorunun çözümü, verilerinizi ve ayırıcılarınızı temsil eden alt sınıflar içeren Kotlin kapalı sınıfı oluşturmaktır. Alternatif olarak, liste öğesi sınıfı ve ayırıcı sınıfınız tarafından genişletilen bir temel sınıf oluşturabilirsiniz.

User öğeden oluşan sayfalı bir listeye ayırıcı eklemek istediğinizi varsayalım. Aşağıdaki snippet'te, örneklerin UserModel veya SeparatorModel olabileceği bir temel sınıfın nasıl oluşturulacağı gösterilmektedir:

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;
    }
  }
}

Veri akışını dönüştürme

Veri akışını yükledikten sonra ve sunmadan önce dönüştürme işlemlerini veri akışına uygulamanız gerekir. Dönüşümlerin aşağıdakileri yapması gerekir:

  • Yüklenen liste öğelerini, yeni temel öğe türünü yansıtacak şekilde dönüştürün.
  • Ayırıcıları eklemek için PagingData.insertSeparators() yöntemini kullanın.

Dönüştürme işlemleri hakkında daha fazla bilgi edinmek için Temel dönüşümleri uygulama bölümüne bakın.

Aşağıdaki örnekte, PagingData<User> akışını ayırıcılar eklenmiş PagingData<UiModel> akışıyla güncellemek için dönüştürme işlemleri gösterilmektedir:

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;
        }
      });
  });

Kullanıcı arayüzündeki ayırıcıları işleyin

Son adım, kullanıcı arayüzünüzü ayırıcı öğe türüne uyacak şekilde değiştirmektir. Ayırıcı öğeleriniz için bir düzen ve görünüm tutucu oluşturun. Ayrıca, birden fazla görünüm sahibi türünü işleyebilmesi için liste adaptörünü RecyclerView.ViewHolder adlı görünüm sahibi türü olarak kullanacak şekilde değiştirin. Alternatif olarak, hem öğe hem de ayırıcı görünüm tutucu sınıflarınızın genişleteceği ortak bir temel sınıf tanımlayabilirsiniz.

Liste bağdaştırıcınızda aşağıdaki değişiklikleri de yapmanız gerekir:

  • Ayırıcı liste öğelerini hesaba katmak için onCreateViewHolder() ve onBindViewHolder() yöntemlerine büyük/küçük harf ekleyin.
  • Yeni bir karşılaştırıcı uygulayın.

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);
  }
}

Yinelenen işlerden kaçının

Kaçınılması gereken önemli sorunlardan biri, uygulamanın gereksiz işler yapmasını sağlamaktır. Veri getirmek pahalı bir işlemdir ve veri dönüşümleri de değerli zaman alabilir. Veriler yüklenip kullanıcı arayüzünde görüntülenmek üzere hazırlandıktan sonra, yapılandırma değişikliği yapılması ve kullanıcı arayüzünün yeniden oluşturulması gerektiğinde kaydedilmelidir.

cachedIn() işlemi, kendisinden önce gerçekleşen dönüşümlerin sonuçlarını önbelleğe alır. Bu nedenle, ViewModelinizdeki son çağrı cachedIn() olmalıdır.

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);

PagingData akışıyla cachedIn() kullanımı hakkında daha fazla bilgi için PagingData akışı oluşturma bölümüne bakın.

Ek kaynaklar

Sayfalama kitaplığı hakkında daha fazla bilgi edinmek için aşağıdaki ek kaynaklara bakın:

Codelab uygulamaları

Sana Özel