Kotlin ile Android: Jetpack Compose Rehberi
Jetpack Compose Nedir?
Jetpack Compose, Google tarafından geliştirilen ve Android kullanıcı arayüzü (UI) oluşturmayı kökten değiştiren modern bir deklaratif UI araç takımıdır. Geleneksel XML tabanlı layout sisteminin yerini alan Compose, tüm arayüzünüzü Kotlin koduyla yazmanıza olanak tanır. Bu yaklaşım sayesinde daha az kod, daha güçlü araçlar ve sezgisel bir Kotlin API ile çalışırsınız.
Geleneksel Android geliştirmede XML dosyalarıyla arayüz tanımlar, ardından findViewById veya View Binding ile bu bileşenlere erişirdiniz. Compose ile bu iki katmanlı yapıya son veriyoruz; her şey tek bir yerde, Kotlin fonksiyonlarında yaşıyor.
Projeye Jetpack Compose Eklemek
Compose kullanmaya başlamak için build.gradle.kts dosyanızda gerekli bağımlılıkları eklemeniz gerekir:
// build.gradle.kts (Module)
android {
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.5.14"
}
}
dependencies {
implementation(platform("androidx.compose:compose-bom:2024.06.00"))
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.material3:material3")
implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.activity:activity-compose:1.9.0")
debugImplementation("androidx.compose.ui:ui-tooling")
}
BOM (Bill of Materials) kullanarak tüm Compose kütüphanelerinin uyumlu versiyonlarını tek bir yerden yönetebilirsiniz. Bu, versiyon çakışmalarını önlemenin en pratik yoludur.
Composable Fonksiyonlar: Temel Yapı Taşı
Compose'un temel birimi @Composable anotasyonu ile işaretlenmiş fonksiyonlardır. Bu fonksiyonlar bir UI bileşenini tanımlar ve herhangi bir değer döndürmez; bunun yerine ekranda görüntülenecek içeriği bildirimsel olarak ifade eder.
@Composable
fun SelamlamaKarti(isim: String) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
) {
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = "Merhaba, $isim!",
style = MaterialTheme.typography.headlineMedium
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "Jetpack Compose dünyasına hoş geldiniz.",
style = MaterialTheme.typography.bodyLarge
)
}
}
}
Dikkat ederseniz, XML'de ayrı ayrı tanımlayacağınız CardView, LinearLayout, TextView gibi bileşenlerin hepsi doğrudan Kotlin fonksiyonları olarak ifade ediliyor. Bu yaklaşımın en büyük avantajı, Kotlin'in tüm gücünü — koşullar, döngüler, değişkenler — doğrudan UI kodunuzda kullanabilmenizdir.
State Yönetimi: remember ve mutableStateOf
Compose'da state (durum) yönetimi en kritik konulardan biridir. Compose, state değiştiğinde ilgili bileşenleri yeniden çizer (recomposition). Bu mekanizmayı doğru anlamak, performanslı ve hatasız uygulamalar geliştirmenin anahtarıdır.
@Composable
fun SayacEkrani() {
var sayac by remember { mutableStateOf(0) }
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = "Sayaç: $sayac",
style = MaterialTheme.typography.displayMedium
)
Spacer(modifier = Modifier.height(16.dp))
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
Button(onClick = { sayac++ }) {
Text("Artır")
}
OutlinedButton(onClick = { sayac = 0 }) {
Text("Sıfırla")
}
}
}
}
Burada birkaç önemli nokta var:
- remember: Recomposition sırasında değerin korunmasını sağlar. Bu olmadan, her yeniden çizimde değer sıfırlanır.
- mutableStateOf: Compose'a bu değerin gözlemlenmesi gerektiğini bildirir. Değer değiştiğinde ilgili composable'lar otomatik olarak yeniden çizilir.
- by delegasyonu:
.valueyazmak yerine doğrudan değişken gibi kullanmanızı sağlar.
Ekran döndürme gibi yapılandırma değişikliklerinde state'i korumak istiyorsanız rememberSaveable kullanmalısınız:
var sayac by rememberSaveable { mutableStateOf(0) }
Modifier Sistemi: Bileşenleri Şekillendirmek
Modifier, Compose'un en güçlü konseptlerinden biridir. Her bileşenin görünümünü, boyutunu, davranışını ve düzenini kontrol eder. Modifier'lar zincir şeklinde uygulanır ve sıra önemlidir.
@Composable
fun StilliButon() {
Box(
modifier = Modifier
.padding(16.dp) // Dış boşluk
.clip(RoundedCornerShape(12.dp))
.background(Color(0xFF6200EE))
.clickable { /* tıklama işlemi */ }
.padding(horizontal = 24.dp, vertical = 12.dp) // İç boşluk
) {
Text(
text = "Özel Buton",
color = Color.White,
fontWeight = FontWeight.Bold
)
}
}
Modifier sıralamasının sonucu doğrudan etkilediğini unutmayın. Örneğin, padding → background ile background → padding tamamen farklı sonuçlar üretir. İlkinde arka plan padding sonrası alanı kaplar; ikincisinde arka plan tüm alanı kaplar ve içerik içe doğru boşluk bırakır.
LazyColumn ile Performanslı Listeler
Compose'da uzun listeler oluşturmak için LazyColumn ve LazyRow kullanılır. Bunlar geleneksel RecyclerView'ın Compose karşılığıdır ve yalnızca ekranda görünen öğeleri oluşturur.
data class Gorev(val id: Int, val baslik: String, val tamamlandi: Boolean)
@Composable
fun GorevListesi(gorevler: List<Gorev>) {
LazyColumn(
contentPadding = PaddingValues(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
items(
items = gorevler,
key = { it.id }
) { gorev ->
GorevSatiri(gorev = gorev)
}
}
}
@Composable
fun GorevSatiri(gorev: Gorev) {
Row(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(8.dp))
.background(MaterialTheme.colorScheme.surfaceVariant)
.padding(12.dp),
verticalAlignment = Alignment.CenterVertically
) {
Checkbox(
checked = gorev.tamamlandi,
onCheckedChange = null
)
Spacer(modifier = Modifier.width(12.dp))
Text(
text = gorev.baslik,
style = MaterialTheme.typography.bodyLarge,
textDecoration = if (gorev.tamamlandi)
TextDecoration.LineThrough else TextDecoration.None
)
}
}
key parametresi performans açısından kritiktir. Compose'un listedeki öğeleri benzersiz şekilde tanımasını sağlar ve gereksiz recomposition'ları önler.
Navigation: Ekranlar Arası Geçiş
Compose'da ekranlar arası navigasyon için Navigation Compose kütüphanesi kullanılır:
@Composable
fun UygulamaNavigasyonu() {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = "ana_sayfa") {
composable("ana_sayfa") {
AnaSayfaEkrani(
onDetayaGit = { id ->
navController.navigate("detay/$id")
}
)
}
composable(
route = "detay/{ogeId}",
arguments = listOf(navArgument("ogeId") { type = NavType.IntType })
) { backStackEntry ->
val ogeId = backStackEntry.arguments?.getInt("ogeId") ?: 0
DetayEkrani(ogeId = ogeId)
}
}
}
Navigasyonda en iyi pratik, event'leri yukarı, state'i aşağı göndermektir (unidirectional data flow). Composable fonksiyonlarınız doğrudan navController'a bağımlı olmamalı; bunun yerine lambda parametreleri aracılığıyla navigasyon tetiklenmelidir.
ViewModel ile Compose Entegrasyonu
Gerçek dünya uygulamalarında state yönetimi için ViewModel kullanmak en sağlıklı yaklaşımdır:
class GorevViewModel : ViewModel() {
private val _gorevler = MutableStateFlow<List<Gorev>>(emptyList())
val gorevler: StateFlow<List<Gorev>> = _gorevler.asStateFlow()
fun gorevEkle(baslik: String) {
val yeniGorev = Gorev(
id = _gorevler.value.size + 1,
baslik = baslik,
tamamlandi = false
)
_gorevler.value = _gorevler.value + yeniGorev
}
fun gorevTamamla(id: Int) {
_gorevler.value = _gorevler.value.map {
if (it.id == id) it.copy(tamamlandi = !it.tamamlandi) else it
}
}
}
@Composable
fun GorevEkrani(viewModel: GorevViewModel = viewModel()) {
val gorevler by viewModel.gorevler.collectAsStateWithLifecycle()
Column {
GorevEklemeFormu(onEkle = viewModel::gorevEkle)
GorevListesi(gorevler = gorevler)
}
}
collectAsStateWithLifecycle kullanarak Flow'u yaşam döngüsüne duyarlı şekilde Compose state'ine dönüştürüyoruz. Bu, uygulama arka plana geçtiğinde gereksiz güncellemeleri önler.
Performans İpuçları
- Stabil tipler kullanın: Compose, parametrelerin değişip değişmediğini kontrol ederek recomposition kararı verir.
data classkullanmak bu karşılaştırmayı güvenilir kılar. - Lambda referanslarını sabitleyin:
onClick = { viewModel.sil(id) }yerinerememberile sararak veya metot referansı kullanarak gereksiz recomposition'ı engelleyin. - derivedStateOf kullanın: Bir state'ten türetilen hesaplamalarda
derivedStateOfkullanarak yalnızca sonuç değiştiğinde recomposition tetikleyin. - LazyColumn'da key belirtin: Liste öğelerinin benzersiz tanımlanmasını sağlayarak diff hesaplamasını optimize edin.
- Layout Inspector kullanın: Android Studio'nun Layout Inspector aracı ile recomposition sayılarını izleyerek darboğazları tespit edin.
Sonuç
Jetpack Compose, Android UI geliştirmeyi daha hızlı, daha okunabilir ve daha sürdürülebilir hale getiriyor. Deklaratif yapısı sayesinde "ne istediğinizi" tanımlarsınız, "nasıl yapılacağını" framework halleder. XML layout dosyaları, adapter sınıfları ve View Binding gibi ek katmanlardan kurtularak doğrudan Kotlin ile düşünmenize olanak tanır. Yeni bir Android projesine başlıyorsanız Compose ile başlamanız, mevcut bir projeniz varsa yeni ekranlarınızı Compose ile yazarak kademeli geçiş yapmanız önerilir.