Paging Library ile Pagination nasıl yapılır?


Listelerin mobil gelişimin önemli bir parçası olduğunu az çok hepimiz biliyoruz. Kullanıcıya göstermek istediğimiz bilgiyi en iyi şekilde listeler ile sağlayabiliriz.
Instagram’daki fotoğraflarınızın, videolarınızın akışını veya Facebook’da ki içeriği düşününce her birinin listeler sayesinde bize iletildiğini görebiliriz.
 Bu yazıda, Android Jetpack üyesi olan Paging Library’i listelerinizde nasıl kullanabilirsiniz örnek bir uygulamada bahsedeceğim. İsterseniz Infinitely Loading Lists nedir ilk ondan bahsedelim.


Infinitely Loading Lists nedir?

Bazen görüntülemek istediğiniz çok fazla veri vardır bunu bir listeye attığınızı düşünün, tek seferde hepsini görüntülemek kulağa pek hoş gelmiyor sanki? 
 Örnek üzerinden gidecek olursak, Instagram, Facebook ya da Reddit tarzı bir uygulamada gezindiğinizi düşünün. Siz daha fazla içerik görmek için ekranı kaydırdıkça yeni içerikler yüklenir bunu sağ tarafta olan scroll göstergesinden de görebilirsiniz. Belli bir noktaya geldikten sonra kendini tekrar yukarıya atar. Bu tarz büyük bir veri kümesine sahip olan uygulamalar asla tek seferde tüm veriyi yüklemez, düşünsenize tüm veriyi tek seferde yüklediğini, internet paketiniz kesinlikle dayanmazdı. Bunun yerine bu tarz büyük verileri barındıran uygulamalar Infinitely Loading List kullanırlar.
 
 Infinitely Loading Lists, kullanıcının etkileşim içerisinde olduğu kadar veriyi çeker ve kullanıcı gezinmeye devam ederse talep üzerine tekrar veriyi çekip listenizin sonuna ekler. Arz talep üzerine kurulu gibi biraz, kulağa daha bir hoş geliyor.

Infinitely Loading Lists avantajları

  • Uygulama sadece ihtiyacı olduğu veriyi indirir.
  • Kullanıcı içeriği daha hızlı alır.
  • Daha az bellek kullanır.
  • Tam veri kümesine gerek duymadığından daha az veri kullanır.
  • Veri güncellemeleri ve yenilemeler sırasında bile, uygulama hızlı bir şekilde yanıt verir.
  • Verileri daha kolay gözlemleyebilir ve güncelleyebilirsiniz.

Hazırsanız başlayalım. Buradan başlangıç projesini indirip Android Studio’ya import ederek devam edebilirsiniz. Android Studio 3.1.3 ya da daha üzeri bir sürümde çalıştırmanız gerekiyor. Ben 3.3 Canary 13 versiyonunu kullanıyorum.

Projeyi açtığınızda bir kaç model sınıfı ve Api interface’imizin içinde Reddit’in API’na call attığımız bir fonksiyon olduğunu göreceksiniz projeyi çalıştırdığınızda de şöyle bir ekranla karşılaşacaksınız.

Reddit API’ı bize Reddit postlarını sayfalar şeklinde döndürdüğünden Paging Library’i etkili biçimde kullanabiliriz.


DataSource Seçimi

DataSource, listeniz için veri yüklemesini yöneten sınıftır. Bazı verileri belirli bir aralıkta veya belirli bir anahtar değeriyle yüklemenizi callback ile sağlar.
Paging Library tarafından sağlanan üç tip DataSource vardır;

  • ItemKeyedDataSource
  • PageKeyedDataSource
  • PositionalDataSource

Burda dikkat etmeniz gereken hangisini seçmeniz gerektiğidir.

#ItemKeyedDataSource: eğer bir sonraki öğeyi(N+1) almak için bir önceki öğenin(N) versini kullanmanız gerekiyorsa seçiminiz bu olmalı. Örneğin, bir tartışma uygulamasındaki yorumlar için, bir sonraki yorumun içeriğini almak için elinizdeki son yorumun keyini iletmeniz gerekir.

#PageKeyedDataSource: çektiğiniz sayfalarda after — before anahtarları varsa yani bir sayfalama için kullanılan key varsa seçiminiz bu olmalı. Örneğin, sosyal medya gönderilerini alıyorsanız, sonraki gönderileri yüklemek için bir sonraki sayfa anahtarına ihtiyaç duyarsınız ki diğer sayfayı alabilesiniz .

#PositionalDataSource: eğer sayfalarınızdan belirli bir aralıktaki verileri çekmek istiyorsanız da bunu kullanmalısınız. Örneğin, 100 ila 120 arasında bulunan ürünleri çekmek gibi.


DataSource Ekleme

İlk işimiz Reddit bilgilerini çekecek bir DataSource oluşturmaktır. Reddit API, Reddit gönderi listesine karşılık gelen bir key döndürüyor. Burdan Reddit gönderilerinin sayfa başına bir key döndürdüğü çıkarımını yapıp, PageKeyedDataSource seçebiliriz.
PostDataSource classını açtığınızd PageKeyDataSource’dan extend
olduğunu, parametre olarakta key türünün String, döndüreceği nesnenin de Post olduğunu göreceksiniz. Henüz implemente edilmemiş, hadi birlikte implemente edelim.

Reddit API’ından postları çekmek için Api sınıfından bir referans oluştuyoruz. loadInitial() ilk çalışacak method gelin içini dolduralım. Aşağıdaki kodu kopyalayıp yapıştırabilirsiniz.

Burda ilk olarak, LoadInitialParams nesnesinde alınan requestedLoadSize parametresini kullanarak API’ye çağrı yapıyoruz. 
Ardından, getPosts methodunu kullanıyoruz bu bize başarılı sonuçlar için onResponse, başarısız sonuçlar için ise onFailure olmak üzere iki tane callback methodu sağlıyor.

OnResponse bloğunda, dönen response başarılı ve null değilse dönen post nesnelerini yukarıda tanımladığımız liveData’mızın value’suna atıyoruz, aynı şekilde bize dönen after ve before keylerini de kullanmak için paging objemize atama işlemini yapıyoruz. Eğer başarılı değilse liveData.value değerine null atama yapıyoruz aynı null atama işlemini onFailure methodumuzda da yapıyoruz.
 callBack.onResult() methodunun aldığı parametreler baktığımızda döndüreceği data, tekrar request için gereken before ve after keyleri olduğunu görebilirsiniz yani liveData’ ya atadığımız değer, paging nesnemizden before ve after keylerini parametre olarak vereceğiz.
 
 Ardından, loadAfter() yöntemini implemente edeceğiz sonraki her aşağıya scroll işlemi için bu method devreye girecek getPosts methodunda ekstra olarak yukarda callback içinde döndürdüğümüz keyi burada after parametresine vereceğiz onun dışında loadInitial methodundan tek farkı callback’de sadece data ve after keyini döndürmesi. Aşağıdaki kodu kopyalayıp yapıştırabilirsiniz

loadBefore() bunun içini doldurmuyorum çünkü bu projede işimize yaramıyor ama loadAfter() ile aynı olacaktı içi tek fark after parametresini değil before parametresini verecektik. Kullanmak istediğimiz diğer bir bileşen ise PagedListAdapter


PagedListAdapter Kullanımı

PostAdapter classına gittiğinizde PagedListAdapter() classından extend olduğunu görürsünüz aldığı ilk parametre Post yani PostDataSource’un döndürdüğü değer diğeri de ViewHolder’ımız ve sonra kullanılmak üzere olan diffUtill.

DiffUtil, RecyclerView.Adapter için yeni bir liste gönderme işlemini kolaylaştırmaya yardımcı olan ara program sınıfıdır. öğelerini etkili bir şekilde güncellemek için notifyItemChanged veya notifyItemInserted yöntemleriyle iletişim kurup callback oluşturur.

Companion object içinde göreceğiniz üzere iki adet method içeriyor. areItemsTheSame() ve areContentsTheSame(). areItemsTheSame, iki öğenin aynı, eşit nesneyi temsil edip etmediğini sorar. Bu genellikle iki öğenin kimliği karşılaştırılarak yapılır. areContentsTheSame, aynı öğenin içeriğinin değişip değişmediğini sorar. areContentsTheSame yalnızca areItemsTheSame true döndürdüğünde çağrılır. ContentsTheSame false değerini döndürürse, bağdaştırıcı içeriği değiştirdiği için öğeyi yeniden oluşturur.

Companion Object içeriğini aşağıdaki kod ile değiştirebilirsiniz.

ViewHolder classımızın içine, gelen veriyle liste rowunu bağlamak için Post nesnesi oluşturup şöyle bir method yazıyoruz.

Hemen ardından onBindViewHolder methodunun içinde bu fonksiyonu çağırıyoruz.

holder.bind(getItem(position))

Klasik onBindViewHolder methodu. getItem ile PagedListAdapter’ın sağladığı Postu alıyoruz.


Bir araya getirme

Elimizde DataSource ve PagedListAdapter var artık bunları birbirine bağlayıp PagedList almanın zamanı geldi. PostViewModel classımıza gidelim ve aşağıdaki kodu olduğu gibi yapıştıralım

  1. PagedList ayarlarını yapmak için PagedList.Config değerine ihtiyacımız var. Bu Config işlemindeki ilk şey, DataSource’un kaç adet öğe getirmeyi deneyeceğidir. 
    setPageSize() methodu ile sayfa başına kaç öğe alacağımızı belirliyoruz farklı bir değerde girebilirsiniz. Getirmeniz gereken öğe sayısını bildiğiniz sürece Paging Library’e yüklenmeyen öğeler için null değerler kullanmasını söyleyebiliriz. PlaceHolder kullanmak, daha fazla içerik yüklerken bunu kullanıcıya gösterme yetisine sahip. KullandığımızReddit API’ının sunulabileceği toplam öğe sayısını bilmediğinizden, bunu false olarak ayarladık.
  2. Kırmızıyla vurgulanan initializedPagedListBuilder methodunu gördüğünüzde endişelenmeyin, birazdan bu methodu oluşturacağız. Bu method, yukarıda tanımladığınız Config ayarlarını alır ve LiveData’yı oluşturmak için LivePagedListBuilder öğesini döndürür.

PagedListAdapter artık yeni öğeler oluşturmak için DataSource’a call atan bir PagedList alıyor.

Şimdi initializedPagedListBuilder() methodunu ekleyelim.

Paging Library, DataSource.Factory ve PagedList.Config alan ve LiveData üreten LivePagedListBuilder sınıfını ortaya çıkarır.

LivePagedListBuilder oluşturmak için, Paging Library ‘e DataSource’u oluşturmak için kullanabileceği bir DataSource.Factory nesnesini iletmeniz gerekir. DataSource.Factory mevcut olan DataSource’u geçersiz kılmamız gerektiğinde Paging Library’in yeni bir DataSource’u yeniden oluşturmasına izin verir.

PostDataSource sınıfı herhangi bir parametre almadığından direkt çağırabiliriz. PagedList aslında değiştirilmiş bir listedir. 
Bitmekte olan öğeler için DataSource ile bütünleşir. 
PagedList’de listenin alt kısmına yaklaşmaya başladıkça, yeni öğeler alınmak için DataSource’a call atılır.

Neden LiveData kullanıyoruz niye direkt PagedList kullanmıyoruz diye sorabilirsiniz. LiveData Observer Patern’i kolayca uygulamamıza ve uygulamamız boyunca yaşam döngüsü bilinciyle veri akışı yapmanıza oizin veren Google tarafından sağlanan mimari kütüphanedir.

PagedList nesnelerinin bir LiveData akışına gereksinim duymasının nedeni, varolan DataSource’u geçersiz kılıp sıfırdan başlamak istemesindendir. 
PagedListe ait LiveData oluşturarak, DataSource’da geçersiz kıldıktan sonra ekstra bir şey yapmamıza gerek kalmıyor.
Bu Sayede LiveData, adaptöre besleyeceğimiz yeni bir PagedList kopyası yayınlıyor ve her şey otomatik olarak çalışmaya devam ediyor.

Son olarak uygulamayı run etmeden önce MainActivity’e gelip şu satırları postList.adapter = adapter kodlarımızın altına ekleyelim

Burda viewMoldel’ımızdan verilerimizi çekip LiveData’yı izliyor, eğer gelen veri null değilse gönderdiği değeri submitList methodu ile oluşturduğumuz adapter’a gönderiyoruz. Eğer null ise ekrana Toast ile mesaj yazdırıyoruz.
 
submitList, bir PagedList’i adaptere bağlayan ve otomatik olarak yükleme işlemini başlatan özel bir PagedListAdapter methodudur.

Artık hazırız uygulamayı run edin ve yavaşça scroll edin. Aşağıya indikçe yeni verilerin geldiğini göreceksiniz.

loadInitial ve loadAfter methodlarıan breakpoint ekleyerek debug ederseniz uygulamada ekranı kaydırdıkça olanları görebilirsiniz.

Okuduğunuz için teşekkür eder iyi çalışmalar dilerim.

iletişim için; celikkrecep@outlook.com

Referanslar
https://github.com/googlesamples/android-architecture-components/tree/master/PagingWithNetworkSamplehttps://developer.android.com/topic/libraries/architecture/paging/
https://developer.android.com/topic/libraries/architecture/paging/ui

https://developer.android.com/topic/libraries/architecture/paging/data