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üştürme için yaygın bir kullanım alanı da liste ayırıcıları eklemektir.

Daha genel olarak, dönüşümleri doğrudan veri akışına uygulamak, depolama alanı 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ına aşina olduğunuz varsayılmaktadır.

Temel dönüşümleri uygulama

PagingData reaktif bir akışta kapsüllendiğinden, verileri yükleme ve sunma arasında verilere dönüşüm işlemleri kademeli olarak uygulayabilirsiniz.

Dönüşümleri akıştaki her PagingData nesnesine uygulamak için dönüşümleri akıştaki bir map() işlemine 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ürme

Veri akışıyla ilgili en temel işlem, verileri farklı bir türe dönüştürmektir. PagingData nesnesine eriştikten sonra, PagingData nesnesi içindeki sayfalandırılmış listedeki her bir öğe üzerinde map() işlemi gerçekleştirebilirsiniz.

Bunun yaygın kullanım alanlarından biri, 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 eşleme 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 veri dönüşümlerinden bir diğeri de kullanıcıdan sorgu dizesi gibi bir giriş almak ve bunu görüntülenecek istek çıkışına dönüştürmektir. Bu ayarı yapmak için kullanıcının sorgu girişini dinlemek ve yakalamak, isteği gerçekleştirmek ve sorgu sonucunu kullanıcı arayüzüne geri göndermek gerekir.

Akış API'si kullanarak sorgu girişini dinleyebilirsiniz. Akış referansını ViewModel dosyanızda tutun. Kullanıcı arayüzü katmanının buna doğrudan erişimi olmamalıdır. Bunun yerine, kullanıcının sorgusunu ViewModel'e bildirecek 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ışındaki 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 yapabilirsiniz. Belirli dönüşüm işlevi, kullanılan dile ve çerçeveye bağlıdır ancak bunların tümü benzer işlevler sağlar.

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şlemleri kullanmak, 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 sorgunun sonuçlarını atar ve yeni aramayı hemen başlatır.

Verileri filtreleme

Sık kullanılan bir diğer işlem de filtrelemedir. Verileri kullanıcıdan gelen ölçütlere göre filtreleyebilir veya diğer ölçütlere göre gizlenmesi gerekiyorsa kullanıcı arayüzünden kaldırabilirsiniz.

Filtre PagingData nesnesi için geçerli olduğundan bu filtre işlemlerini map() çağrısının içine yerleştirmeniz gerekir. Veriler PagingData'ten filtrelendikten sonra yeni PagingData örneği, görüntülenmesi için 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ı ekleme

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

Sayfalandırılmış listenize ayırıcı eklemek için üç adım gerekir:

  1. Kullanıcı arayüzü modelini, ayırıcı öğeleri barındıracak şekilde dönüştürün.
  2. Verileri yükleme ile sunma arasında ayırıcıları dinamik olarak eklemek için veri akışını 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ürme

Sayfalama kitaplığı, gerçek liste öğeleri olarak RecyclerView içine liste ayırıcıları ekler. Ancak ayırıcı öğelerinin, farklı bir kullanıcı arayüzüne sahip farklı bir ViewHolder türüne bağlanabilmesi için listedeki veri öğelerinden ayırt edilebilir olması gerekir. Çözüm, verilerinizi ve ayırıcılarınızı temsil edecek alt sınıflar içeren bir Kotlin mühürlü sınıf oluşturmaktır. Alternatif olarak, liste öğesi sınıfınız ve ayırıcı sınıfınız tarafından genişletilen bir temel sınıf oluşturabilirsiniz.

User öğelerinin bulunduğu 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 ve sunmadan önce veri akışına dönüşümler uygulamanız gerekir. Dönüşümler şunları yapmalıdır:

  • 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üşüm işlemleri hakkında daha fazla bilgi edinmek için Temel dönüşümleri uygulama başlıklı makaleyi inceleyin.

Aşağıdaki örnekte, PagingData<User> akışını ayırıcılar eklenmiş bir PagingData<UiModel> akışına güncellemek için kullanılan 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ünde ayırıcıları işleme

Son adım, kullanıcı arayüzünüzü ayırıcı öğe türüne uygun olacak şekilde değiştirmektir. Ayırıcı öğeleriniz için bir düzen ve görüntü tutucu oluşturun ve liste bağdaştırıcısı birden fazla görüntü tutucu türünü işleyebilmek için görüntü tutucu türü olarak RecyclerView.ViewHolder kullanacak şekilde değiştirin. Alternatif olarak, hem öğe hem de ayırıcı görüntü tutucu sınıflarınızın genişlettiğ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 duyarlılığı ekleyin.
  • Yeni bir karşılaştırma ifadesi 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 çalışmalardan kaçının

Önlemeniz gereken önemli sorunlardan biri, uygulamanın gereksiz işler yapmasıdır. Veri getirme pahalı bir işlemdir ve veri dönüştürme işlemleri de değerli zamanınızı alabilir. Veriler yüklendikten ve kullanıcı arayüzünde gösterilmeye hazırlandıktan sonra, yapılandırma değişikliği olması ve kullanıcı arayüzünün yeniden oluşturulması durumunda veriler kaydedilmelidir.

cachedIn() işlemi, kendisinden önce gerçekleşen tüm dönüşümlerin sonuçlarını önbelleğe alır. Bu nedenle, cachedIn(), ViewModel'inizdeki son çağrı 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);

cachedIn() öğesini PagingData akışıyla kullanma hakkında daha fazla bilgi için PagingData akışı oluşturma başlıklı makaleyi inceleyin.

Ek kaynaklar

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

Codelab uygulamaları