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

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:

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

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:

pager.flow // Type is Flow<PagingData<User>>.
  .map { pagingData ->
    pagingData.map { user -> UiModel(user) }
  }

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.

private val queryFlow = MutableStateFlow("")

fun onQueryChanged(query: String) {
  queryFlow.value = 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.

val querySearchResults: Flow<User> = queryFlow.flatMapLatest { query ->
  // The database query returns a Flow which is output through
  // querySearchResults
  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.

pager.flow // Type is Flow<PagingData<User>>.
  .map { pagingData ->
    pagingData.filter { user -> !user.hiddenFromUi }
  }

Liste ayırıcıları ekleme

Paging kitaplığı, dinamik liste ayırıcılarını destekler. Ayırıcıları doğrudan veri akışına, düzeninizdeki composable'lar olarak ekleyerek liste okunabilirliğini artırabilirsiniz. Bu nedenle ayırıcılar, tam etkileşim, stil ve erişilebilirlik semantiği sağlayan tam özellikli composable'lardır.

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. Bunu yapmanın bir yolu, veri öğenizi ve ayırıcınızı tek bir kapalı sınıfa sarmaktır. Bu sayede kullanıcı arayüzü, aynı listede birden fazla öğe türünü işleyebilir.
  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ı kullanıcı arayüzüne gerçek liste öğeleri olarak ekler. Ancak, her iki composable türünün de belirgin şekilde oluşturulmasını sağlamak için ayırıcı öğelerin 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:

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

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:

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

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. Lazy düzende, her bir yayınlanan UiModel öğesinin türünü kontrol ederek birden fazla öğe türünü işleyebilirsiniz. Sayfalandırılmış verilerinizde yineleme yaparken uygun composable'ı çağırmak için when ifadesini kullanın. Bu, veri öğeleri ve ayırıcılar için farklı bir kullanıcı arayüzü sağlamanıza olanak tanır.

@Composable fun UserList(pagingItems: LazyPagingItems) {
  LazyColumn {
    items(
      count = pagingItems.itemCount,
      key = { index ->
        val item = pagingItems.peek(index)
        when (item) {
          is UiModel.UserModel -> item.user.id
          is UiModel.SeparatorModel -> item.description
          else -> index
        }
      }
    ) { index ->
      when (val item = pagingItems[index]) {
        is UiModel.UserModel -> UserItemComposable(item.user)
        is UiModel.SeparatorModel -> SeparatorComposable(item.description)
        null -> PlaceholderComposable()
      }
    }
  }
}

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üklenip kullanıcı arayüzünde gösterilmeye hazırlandıktan sonra, yapılandırmada değişiklik 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. Genellikle bu operatörü, Flow'yi composable'larınıza sunmadan önce ViewModel içinde uygularsınız.

Önbelleği doğru şekilde yönetmek için CoroutineScope öğesini cachedIn()'ye iletin. Aşağıdaki örnekte viewModelScope kullanılarak gösterilmiştir.

pager.flow // Type is Flow<PagingData<User>>.
  .map { pagingData ->
    pagingData.filter { user -> !user.hiddenFromUi }
      .map { user -> UiModel.UserModel(user) }
  }
  .cachedIn(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:

Belgeler

İçeriği görüntüleme