Android Jetpack Compose – Pagination kullanımı

Paginator adlı Interface’i üretelim ve içerisine;

interface Paginator<Item, T> {
    suspend fun loadNextItems()
    fun reset()
}

loadNextItems yeni verilerin çekilmesi, reset ise sayfalandırmanın resetlenmesi için kullanılacak.

ListItem adında data class üretelim ve fieldlarını tanımlayalım;

data class ListItem(
    val title:String,
    val description:String
)

Repository adında bir sınıf oluşturalım.

class Repository {
}

Bu sınıfın içerisine verilerin yüklenmesi için remoteDataSource metodu üretelim.

private val remoteDataSource = (1..100).map{
        ListItem(title="Başlık $it",description="Açıklama $it")
}

Verilerin listelenebilmesi adın getItems adlı bir metot üretelim.

suspend fun getItems(page:Int,pageSize:Int): Result<List<ListItem>> {
        delay(2000L)
        val startingIndex = page * pageSize
        return if(startingIndex + pageSize <= remoteDataSource.size){
            Result.success(
                remoteDataSource.slice(startingIndex until startingIndex + pageSize)
            )
        }else Result.success(emptyList())
}

Burada delay(2000L) ile 2 saniye beklemesini sağlıyoruz. Bu metot çağırıldığında çalışacağı için her çağırıldığında yapılacak olan görevi tanımlıyoruz.

startingIndex içerisinde kaçıncı sayfada olduğumuz bilgisinin, toplam gösterilecek olan yeni veri sayısı ile çarpılması sonucu oluşuyor. Örn: page = 1 ve pageSize = 20 = 1*20 = 20

Altta bulunan if(startingIndex + pageSize <= remoteDataSource.size) ise toplam listelenebilecek eleman sayısından az bir değer oluşması durumunda yeni verilerin yüklenmesi sağlanıyor. Aksi durumda hiçbir eylem gerçekleştirilmemesi sağlanmış oluyor.

ScreenState adlı bir veri sınıfı oluşturup içerisine gerekli değişkenlerimizi tanımlayalım;

data class ScreenState(
    val isLoading:Boolean=false,
    val items:List<ListItem> = emptyList(),
    val error:String?=null,
    val endReached:Boolean = false,
    val page:Int=0
)

Projemize androidx lifecycle’ında bulunan viewmodel compose’u projemize dahil edelim.

implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.4.1

DefaultPaginator adlı bir sınıf oluşturalım. Bu sınıf onLoadUpdate, onRequest, getNextKey, initialKey, onError, onSuccess olaylarını tanımlayabilmemiz için kullanılacak.

class DefaultPaginator<Key,Item> (
    private val initialKey:Key,
    private inline val onLoadUpdated:(Boolean) -> Unit,
    private inline val onRequest: suspend (nextKey:Key) -> Result<List<Item>>,
    private inline val getNextKey: suspend (List<Item>) -> Key,
    private inline val onError: suspend (Throwable?) -> Unit,
    private inline val onSuccess: suspend (items: List<Item>, newKey:Key) -> Unit
):Paginator<Key,Item>{

    private var currentKey= initialKey
    private var isMakingRequest = false

    override suspend fun loadNextItems() {
        if(isMakingRequest){
            return
        }
        isMakingRequest = true
        onLoadUpdated(true)
        var result = onRequest(currentKey)
        isMakingRequest = false
        val items = result.getOrElse {
            onError(it)
            onLoadUpdated(false)
            return
        }
        currentKey = getNextKey(items)
        onSuccess(items,currentKey)
        onLoadUpdated(false)
    }

    override fun reset() {
        currentKey = initialKey
    }

}

MainViewModel adlı bir sınıf oluşturup bu sınıfı ViewModel sınıfından türetelim.

class MainViewModel: ViewModel() {
}

Repository içerisindeki verileri çekebilmek için tanımlama yapıp state üretelim.

private val repository = Repository()
var state by mutableStateOf(ScreenState())

DefaultPaginator sınıfından bir nesne üretelim. Bu nesne verilerin hangi sıklıkla yenileneceğini/yükleneceğini tanımladığımız alanı ifade edecektir.

private val paginator = DefaultPaginator(
        initialKey = state.page,
        onLoadUpdated = {
            state = state.copy(isLoading = it)
        },
        onRequest = { nextPage ->
            repository.getItems(nextPage,20)
        },
        getNextKey = {
            state.page + 1
        },
        onError = {
            state = state.copy(error = it?.localizedMessage)
        },
        onSuccess = {items, newKey ->
            state = state.copy(
                items = state.items + items,
                page = newKey,
                endReached = items.isEmpty()
            )
        }
)

initialKey bulunduğumuz sayfayı, onLoadUpdated yükleme olduğunda ScreenState sınıfındaki isLoading değerini güncelliyor, onRequest istekte bulunulduğunda yani sayfanın en altına inip 20 şer veri gördüğümüzde + 20 veri daha yüklenmesi için istekte bulunduğumuz zamanı, getNextKey istek sonrasında sayfa sayısının arttırılmasını, onError bir hata oluşması zamanını, onSuccess ise işlem başarılı olduğu zamanı temsil ediyor.

Son olarak verilerin ComponentActivity sınıfı içerisinde çağırılması işlemi kalıyor;

val viewModel = viewModel<MainViewModel>()
                val state = viewModel.state
                LazyColumn(
                    modifier = Modifier.fillMaxSize()
                ){
                    items(state.items.size){ i ->
                        val item = state.items[i]
                        if(i>=state.items.size -1 && !state.endReached && !state.isLoading){
                            viewModel.loadNextItems()
                        }
                        Column (
                            modifier = Modifier
                                .fillMaxWidth()
                                .padding(16.dp)
                                ){
                            Text(
                                text = item.title,
                                fontSize = 20.sp,
                                color = Color.Black
                            )
                            Spacer(modifier = Modifier.height(8.dp))
                            Text(text = item.description)
                            
                        }
                    }
                    item {
                        if(state.isLoading){
                            Row (
                                modifier = Modifier
                                    .fillMaxWidth()
                                    .padding(8.dp),
                                horizontalArrangement = Arrangement.Center
                            ){
                                CircularProgressIndicator()
                            }
                        }
                    }
}

Keyifli çalışmalar dilerim.

You may also like...

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir