serhattastan.com
/
← Yazılar

14 Temmuz 2024·6 dk okuma

Jetpack Compose Lazy Column ve Geleneksel RecyclerView Üzerine

Jetpack Compose Lazy Column ve Geleneksel RecyclerView Üzerine

1. Yapısal Farklılıklar

RecyclerView’da Yapı

RecyclerView, bir liste öğesini göstermek için geleneksel olarak bir dizi bileşene dayanır: Adapter, ViewHolder ve LayoutManager. RecyclerView’ın temel yapı taşlarından olan Adapter, verilerin RecyclerView üzerinde nasıl gösterileceğini tanımlar. Her bir öğe için XML layout dosyası tanımlaman gerekir ve bu öğe layout’unu ViewHolder sınıfında bağlarsın. Bu süreç, özellikle büyük projelerde kodun karmaşıklaşmasına neden olabilir.

Bir örnek üzerinden konuşalım. Diyelim ki bir Flashcard uygulaması yapıyoruz ve kartlarımızı RecyclerView ile göstermek istiyoruz. İlk adım olarak her bir kart için bir layout dosyası hazırlıyoruz:

<!-- item_card.xml -->
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:padding="16dp"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    
    <TextView
        android:id="@+id/termTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Term"/>

    <TextView
        android:id="@+id/definitionTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Definition"/>
</LinearLayout>

Ardından, bu layout’u Adapter sınıfımıza bağlamamız gerekiyor:

class CardAdapter(private val cards: List<Card>) : RecyclerView.Adapter<CardAdapter.CardViewHolder>() {

    inner class CardViewHolder(private val binding: ItemCardBinding) : RecyclerView.ViewHolder(binding.root) {
        fun bind(card: Card) {
            binding.termTextView.text = card.term
            binding.definitionTextView.text = card.definition
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CardViewHolder {
        val binding = ItemCardBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return CardViewHolder(binding)
    }

    override fun onBindViewHolder(holder: CardViewHolder, position: Int) {
        holder.bind(cards[position])
    }

    override fun getItemCount() = cards.size
}

Bu yapıda CardAdapter sınıfı, RecyclerView’a verileri bağlar. Ancak, fark ettiysen her bir öğe için manuel olarak hem layout hem de kod kısmında çalışma yapıyoruz. Bu yapıyı birden fazla öğe için oluşturmak oldukça zahmetli olabiliyor.

LazyColumn’da Yapı

Jetpack Compose’da ise tüm bu yapılar çok daha basit hale geliyor. LazyColumn adlı listeleme bileşeni, RecyclerView’ın karmaşık yapısını tamamen ortadan kaldırıyor. Adapter, ViewHolder ve LayoutManager gibi bileşenlerle uğraşmadan, liste öğelerini doğrudan Composable fonksiyonlarla tanımlayabiliyoruz.

Bir örnekle açıklayalım. Aynı Flashcard uygulamasını Jetpack Compose ile yapalım:

@Composable
fun FlashCardList(cards: List<Card>) {
    LazyColumn {
        items(cards) { card ->
            CustomCardItem(card)
        }
    }
}

@Composable
fun CustomCardItem(card: Card) {
    Column(
        modifier = Modifier
            .padding(16.dp)
            .fillMaxWidth()
    ) {
        Text(text = card.term, fontWeight = FontWeight.Bold)
        Spacer(modifier = Modifier.height(8.dp))
        Text(text = card.definition, color = Color.Gray)
    }
}

Burada LazyColumn ile her bir kartı listelemek için sadece birkaç satır koda ihtiyaç duyuyoruz. Adapter ya da ViewHolder gibi ek yapılar olmadan, UI bileşenlerini doğrudan Kotlin kodu içinde tanımlıyoruz. Jetpack Compose’un getirdiği bu sade yapı, özellikle UI tasarımı üzerinde daha fazla kontrol sağlıyor ve çok daha hızlı gelişiyor.

Bu gibi basit örneklerde aradaki farkı küçümseyebilirsin. Bu farkı daha iyi anlaman ve bu yöntemlerin ikisinide iyice öğrenmen için iki RecyclerView’ı veya iki LazyColumn’u iç içe kullandığın bir yapı kodlamanı tavsiye ederim. Ne demek istediğimi ozaman çok daha iyi anlayacağına eminim. :D Eğer vaktim yok diyorsan yazımın sonunda paylaştığım projelere göz atmayı unutma.

2. Performans Karşılaştırması

Performans, büyük uygulamalarda en önemli konulardan biridir. RecyclerView, performans konusunda oldukça güçlüdür çünkü görünüm geri dönüşüm (view recycling) mekanizması ile çalışır. Görünmeyen öğeler bellekte saklanmaz ve yeniden kullanılır, bu da büyük listelerde bellek kullanımını optimize eder. Ancak bu yapı, manuel olarak Adapter ve ViewHolder sınıflarıyla optimize edilmelidir.

Jetpack Compose’da ise recomposition dediğimiz bir mekanizma kullanılır. Yani sadece ekranın güncellenmesi gereken kısmı yeniden oluşturulur. Bu, performans açısından büyük bir avantaj sağlar. LazyColumn’daki öğeler ekranda görünmedikçe oluşturulmaz, böylece gereksiz bellek kullanımı olmaz.

Performans açısından iki yapı da oldukça güçlüdür. Ancak Jetpack Compose, bu optimizasyonları içsel olarak hallettiği için geliştirici daha az manuel iş yapar. Örneğin, aşağıda basit bir performans optimizasyonu sağlayan LazyColumn örneği görebilirsin:

@Composable
fun OptimizedLazyColumn(cards: List<Card>) {
    LazyColumn {
        items(cards) { card ->
            // Sadece ihtiyaç duyulan öğeler yeniden oluşturulur.
            CustomCardItem(card)
        }
    }
}

RecyclerView’da ise performansı optimize etmek için notifyDataSetChanged() ve notifyItemChanged() gibi yöntemler manuel olarak uygulanmalıdır.

3. UI Özelleştirme

UI özelleştirme, özellikle zengin kullanıcı arayüzleri oluştururken önemlidir. RecyclerView’da bir liste öğesini özelleştirmek için XML dosyası hazırlaman gerekir. Bu dosyayı ViewHolder ile bağladıktan sonra, her bir öğenin stilini ve düzenini kontrol edersin. Ancak UI’yı özelleştirmek karmaşık bir süreç olabilir çünkü sürekli olarak XML ve Kotlin/Java arasında gidip gelmek zorundasın.

Jetpack Compose’da bu süreç çok daha basit. Modifier, Box, Column ve Row gibi Compose bileşenleri ile arayüzü tamamen Kotlin kodu içinde yönetebilirsin. Böylece her öğeyi istediğin gibi düzenleyebilir, dinamik olarak değiştirebilirsin. Örneğin, bir kart öğesini özelleştirelim:

@Composable
fun CustomCardItem(card: Card, backgroundColor: Color) {
    Box(
        modifier = Modifier
            .fillMaxWidth()
            .background(backgroundColor)
            .padding(16.dp)
    ) {
        Column {
            Text(text = card.term, fontWeight = FontWeight.Bold, color = Color.White)
            Spacer(modifier = Modifier.height(8.dp))
            Text(text = card.definition, color = Color.LightGray)
        }
    }
}

Burada her öğenin arka plan rengini kolayca değiştirebiliyoruz ve Modifier ile arayüz üzerinde tam kontrol sahibiyiz. RecyclerView’da bunu yapmak için XML dosyası üzerinde ekstra stil tanımlamaları yapmamız gerekirdi.

Ayrıca Android geliştirmeye yeni başladığım zamanlarda özelleştirdiğim bir butona tıklama animasyonu verirken çektiklerimi bir ben bilirim. Neyseki Compose’da nekadar özelleştirirsen özelleştir bütün bileşenlerde eğer onClick metodu kullanılıyorsa tıklama efekti otomatik ekleniyor :D

4. Animasyonlar

Animasyonlar, kullanıcı arayüzünü daha dinamik ve çekici hale getirir. RecyclerView’da animasyonları yönetmek için ItemAnimator ve LayoutTransition gibi yapılar kullanılır. Ancak bu animasyonları manuel olarak yönetmek ve uygulamak genellikle zaman alıcıdır. Örneğin, öğe ekleme veya silme animasyonları için özel animasyon sınıfları yazmamız gerekebilir.

Jetpack Compose’da ise animasyonlar oldukça basit bir şekilde uygulanabilir. animateContentSize() veya AnimatedVisibility() gibi hazır animasyon fonksiyonları, arayüze animasyon eklemeyi çok kolay hale getirir. İşte bir örnek:

@Composable
fun AnimatedCardItem(isExpanded: Boolean) {
    Column(
        modifier = Modifier
            .fillMaxWidth()
            .padding(16.dp)
            .animateContentSize()  // Dinamik olarak genişleme animasyonu
    ) {
        Text(text = "Başlık", fontWeight = FontWeight.Bold)
        if (isExpanded) {
            Text(text = "Detaylı açıklama", color = Color.Gray)
        }
    }
}

Gördüğün gibi, animateContentSize() ile bir öğenin boyutunu dinamik olarak değiştirebiliyoruz ve bu değişiklik animasyonlu bir şekilde gerçekleşiyor.

5. State Yönetimi

RecyclerView’da state yönetimi oldukça manuel bir işlemdir. Örneğin, bir öğe seçildiğinde veya güncellendiğinde notifyDataSetChanged() gibi yöntemler kullanarak RecyclerView’a bildirimde bulunmak zorundasın. Bu süreç, büyük ve dinamik listelerde yönetmesi zor hale gelebilir.

Jetpack Compose’da ise state yönetimi çok daha basit ve doğal bir şekilde gerçekleştirilir. remember ve mutableStateOf gibi araçlar ile bir öğenin durumu değiştiğinde UI otomatik olarak yeniden oluşturulur. Örneğin, bir öğenin seçildiği durumu yönetmek için şöyle bir yapı kullanabiliriz:

@Composable
fun SelectableCardItem(card: Card, isSelected: Boolean, onClick: () -> Unit) {
    val backgroundColor = if (isSelected) Color.Green else Color.Gray

Box(
        modifier = Modifier
            .fillMaxWidth()
            .background(backgroundColor)
            .padding(16.dp)
            .clickable { onClick() }
    ) {
        Column {
            Text(text = card.term, fontWeight = FontWeight.Bold)
            Text(text = card.definition, color = Color.LightGray)
        }
    }
}

Bu yapı, bir kartın seçildiğinde nasıl güncelleneceğini otomatik olarak yönetir. RecyclerView’da bunu manuel olarak yapmak zorunda kalırsın.

6. Hangi Durumlarda Hangisini Seçmeliyiz?

Küçük projelerde ve dinamik arayüz gerektiren uygulamalarda Jetpack Compose mükemmel bir seçimdir. Daha az kod yazman gerekir ve UI bileşenlerini yönetmek çok daha kolay olur. Özellikle yeni başlayanlar için Compose, hızlı bir başlangıç yapmak için idealdir.

Ancak büyük ve karmaşık projelerde, özellikle düşük seviyeli performans optimizasyonu gerekiyorsa, RecyclerView hâlâ güçlü bir çözüm sunar. Büyük veri setleri ile çalışırken görünüm geri dönüşüm mekanizması sayesinde bellek yönetimi daha verimli olur.

7. Sonuç

Jetpack Compose, modern Android geliştirme için harika bir araç. Performans, esneklik ve kullanım kolaylığı açısından büyük avantajlar sunar. Özellikle dinamik ve hızlı gelişen projelerde Compose, geliştiriciye büyük bir hız kazandırır. Ancak, büyük ve karmaşık projelerde RecyclerView’ın sağladığı ince ayar ve kontrol avantajlarını da göz ardı etmemek gerekir. Doğru araçları doğru projelerde kullanmak, Android uygulama geliştirme sürecini çok daha verimli hale getirecektir.

Serhat Taştan

Yazan

Serhat Taştan

Mobile & Web Developer & Trainer & CATH Lab Nurse

Mühendislik disiplinine sahip bir yazılım geliştirici ve Başakşehir Çam ve Sakura Şehir Hastanesi'nde (Anjiyo Ünitesi) görev yapan bir hemşireyim. Unvanlara takılmadan, klinik deneyimimi ve yazılım yeteneklerimi birleştirerek problem odaklı çözümler üretiyorum. Çok yönlülüğün gücüne inanarak, ister hastanede ister klavye başında süreçleri iyileştiriyor ve somut sonuçlar yaratıyorum.