Veri akışlarını (görünümler) dönüştürme

Kavramlar ve Jetpack Compose uygulaması

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

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, Paging kitaplığının temel kullanımı hakkında bilgi sahibi 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 verilerde artımlı olarak dönüştürme işlemleri uygulayabilirsiniz.

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

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

Bir veri akışındaki en temel işlem, verileri farklı bir türe dönüştürmektir. PagingData nesnesine erişiminiz olduğunda, PagingData nesnesindeki sayfalandırılmış listedeki her bir öğe üzerinde map() işlemi gerçekleştirebilirsiniz.

Bu özelliğin yaygın kullanım alanlarından biri, bir ağ veya veritabanı katmanı nesnesini özellikle kullanıcı arayüzü katmanında kullanılan bir nesneye eşlemektir. Aşağıdaki örnekte bu tür bir harita işleminin nasıl uygulanacağı gösterilmektedir:

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

Diğer bir yaygın veri dönüştürme işlemi ise kullanıcıdan alınan bir girişi (ör. sorgu dizesi) dönüştürerek istek çıkışında göstermektir. Bunu ayarlamak 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.

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

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 istenen 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 bağlı olarak değişir ancak hepsi benzer işlevler sunar.

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

Verileri filtreleme

Diğer bir yaygın 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 nesnesine uygulandığından bu filtre işlemlerini map() çağrısının içine yerleştirmeniz gerekir. Veriler PagingData dışına filtrelendikten sonra, yeni PagingData örneği görüntülenmek üzere kullanıcı arayüzü katmanına iletilir.

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

Paging kitaplığı, dinamik liste ayırıcılarını destekler. Doğrudan veri akışına RecyclerView liste öğeleri olarak ayırıcılar ekleyerek liste okunabilirliğini artırabilirsiniz. Bu nedenle ayırıcılar, tam özellikli ViewHolder nesnelerdir. Bu nesneler, etkileşim, erişilebilirlik odağı ve View tarafından sağlanan diğer tüm özellikleri etkinleştirir.

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

  1. Ayırıcı öğeleri barındırmak için kullanıcı arayüzü modelini dönüştürün.
  2. Veri akışını, veriler yüklenirken ve sunulurken ayırıcıları dinamik olarak ekleyecek şekilde dönüştürün.
  3. Ayırıcı öğeleri işlemek için kullanıcı arayüzünü güncelleyin.

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

Paging kitaplığı, liste ayırıcılarını RecyclerView içine gerçek liste öğeleri olarak ekler ancak ayırıcı öğelerin, farklı bir ViewHolder türüne bağlanabilmeleri için listedeki veri öğelerinden ayırt edilebilir olması gerekir. Çözüm, verilerinizi ve ayırıcılarınızı temsil etmek için alt sınıfları olan bir Kotlin sealed class 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 öğeden oluşan sayfalandırılmış 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:

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

Aşağıdaki örnekte, ayraçlar eklenerek PagingData<User> akışının PagingData<UiModel> akışına güncellenmesi için kullanılan dönüştürme işlemleri gösterilmektedir:

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 uyacak şekilde değiştirmektir. Ayırıcı öğeleriniz için bir düzen ve görünüm tutucu oluşturun. Ardından, birden fazla görünüm tutucu türünü işleyebilmesi için liste bağdaştırıcısını RecyclerView.ViewHolder görünüm tutucu türünü kullanacak şekilde değiştirin. Alternatif olarak, hem öğenizin hem de ayırıcı görünüm tutucu sınıflarınızın genişlettiği ortak bir temel sınıf tanımlayabilirsiniz.

Ayrıca liste bağdaştırıcınızda aşağıdaki değişiklikleri 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ırma ifadesi uygulayın.

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

Uygulamanın gereksiz işler yapmasını önlemek önemli bir konudur. Veri getirme işlemi maliyetli bir işlemdir ve veri dönüşümleri de değerli zamanınızı alabilir. Veriler yüklendikten ve kullanıcı arayüzünde gösterilmeye hazır hâle getirildikten sonra, yapılandırma değişikliği olması ve kullanıcı arayüzünün yeniden oluşturulması ihtimaline karşı 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.

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()'yı PagingData akışıyla kullanma hakkında daha fazla bilgi için Sayfalama verileri akışı oluşturma başlıklı makaleyi inceleyin.

Ek kaynaklar

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

Codelab uygulamaları