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:
- Kullanıcı arayüzü modelini, ayırıcı öğeleri barındıracak şekilde dönüştürün.
- Verileri yükleme ile sunma arasında ayırıcıları dinamik olarak eklemek için veri akışını 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ü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()
veonBindViewHolder()
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ı
Sizin için önerilenler
- Not: JavaScript kapalıyken bağlantı metni gösterilir
- Sayfaya ayrılmış verileri yükleme ve görüntüleme
- Sayfalandırma uygulamanızı test etme
- Yükleme durumlarını yönetme ve sunma