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:
- Kullanıcı arayüzü modelini, ayırıcı öğelere yer verecek şekilde dönüştürün.
- Veri akışı ile verileri sunma arasındaki ayırıcıları dinamik olarak eklemek için dönüştürün.
- 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()
veonBindViewHolder()
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
- Android Mimari Bileşenleri Sayfa örneği
- Veritabanı ve Ağ örneğiyle Android Mimarisi Bileşenleri Sayfalama
Sizin için önerilenler
- Not: Bağlantı metni JavaScript kapalıyken gösterilir
- Sayfalı verileri yükleme ve görüntüleme
- Sayfalama uygulamanızı test etme
- Yükleme durumlarını yönetme ve gösterme