
Android uygulama geliştirmeye adım atarken yapılabilecek en iyi başlangıç, çalışan somut bir ilk proje üretmektir. Bu rehberde, bir Vücut Kitle İndeksi (VKİ) hesaplayıcı uygulaması geliştirerek Android dünyasına giriş yapıyoruz. Değişimi ve pratikliği daha iyi kavramak için, aynı projeyi önce geleneksel yaklaşım olan XML ve Java ile nasıl yapardık ona bakacak, ardından günümüz modern standardı olan Kotlin ve Jetpack Compose ile sıfırdan adım adım inşa edeceğiz.
Vücut Kitle İndeksi, kişinin kilosunun boyuna göre normal olup olmadığını kabaca anlatan bir değerdir. Tek başına kesin bir sağlık ölçütü değildir ama yaygın olarak zayıflık, normallik, fazla kilo ve obezite aralıklarını belirlemek için kullanılır. Uygulamamız bu basit formülü uygulayacak:
VKİ = Kilo (kg) / Boy (m)²
Örnek: 70 kg ağırlığında ve 175 cm (1.75 m) boyunda bir kişi için:
En Sık Yapılan Hata: Boy Birimi
Kullanıcı boyunu santimetre (cm) cinsinden girer: "175". Ama formül, boyun metre cinsinden olmasını ister. Kodumuzda boy / 100 yaparak santimetreyi metreye çevireceğiz. Bu çevirmeyi unutursak sonuç inanılmaz derecede küçük çıkar!
Hesaplanan VKİ değeri, aşağıdaki eşik değerlere göre bir durum etiketine dönüştürülür. Bu sınıflandırma, kodumuzda Kotlin'in güçlü when yapısıyla yapılacak:
| VKİ Aralığı | Kategori | Kodda Karşılığı |
|---|---|---|
| 18.5'ten küçük | Zayıf | vki < 18.5f -> "Zayıf" |
| 18.5 – 24.9 | Normal Kilo | vki < 25f -> "Normal Kilolu" |
| 25 – 29.9 | Fazla Kilolu | vki < 30f -> "Fazla Kilolu" |
| 30 ve üzeri | Obez | else -> "Obez" |
Android geliştirme dünyasında büyük bir devrim yaşandı. Bu değişimi anlamak, neden Compose ile uygulama geliştirdiğimizi kavramak açısından son derece önemlidir.
Aşağıdaki görselde, bu eğitimde Android telefonumuzda çalışan hâlini adım adım inşa edeceğimiz VKİ Hesaplayıcı uygulamasının arayüzü yer alıyor:

Peki bu ekranı geçmişte nasıl yapıyorduk, bugün Compose ile nasıl yapıyoruz? Birlikte bakalım.
Eski Yöntem: XML ve Java
Geleneksel Android geliştirmede arayüz tasarımı ile iş mantığı birbirinden tamamen ayrı iki dosyada yürütülürdü. Bu yöntemle yeni bir proje açarken:
Yukarıdaki arayüzü eski yöntemle oluşturmak isteseydik şu kodları yazmamız gerekirdi:
1. Arayüz Dosyası (activity_main.xml):
0 1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 android:layout_width="match_parent" 3 android:layout_height="match_parent" 4 android:orientation="vertical" 5 android:padding="16dp"> 6 7 <TextView 8 android:layout_width="wrap_content" 9 android:layout_height="wrap_content" 10 android:text="VKİ Hesaplayıcı" 11 android:textSize="20sp" /> 12 13 <TextView 14 android:layout_width="wrap_content" 15 android:layout_height="wrap_content" 16 android:text="Boyunuz (cm)" /> 17 18 <EditText 19 android:id="@+id/etBoy" 20 android:layout_width="match_parent" 21 android:layout_height="wrap_content" 22 android:hint="Örn: 175" 23 android:inputType="numberDecimal" /> 24 25 <TextView 26 android:layout_width="wrap_content" 27 android:layout_height="wrap_content" 28 android:text="Kilonuz (kg)" /> 29 30 <EditText 31 android:id="@+id/etKilo" 32 android:layout_width="match_parent" 33 android:layout_height="wrap_content" 34 android:hint="Örn: 70" 35 android:inputType="numberDecimal" /> 36 37 <Button 38 android:id="@+id/btnHesapla" 39 android:layout_width="match_parent" 40 android:layout_height="wrap_content" 41 android:text="VKİ Hesapla" /> 42 43 <TextView 44 android:id="@+id/tvSonuc" 45 android:layout_width="wrap_content" 46 android:layout_height="wrap_content" 47 android:text="Sonuç: -" 48 android:textSize="18sp" 49 android:textStyle="bold" 50 android:layout_marginTop="16dp" /> 51 52 </LinearLayout>
2. Mantık Dosyası (MainActivity.java):
0 1 package com.example.vkihesaplayici; // Kendi paket adınıza göre güncelleyebilirsiniz 2 3 import android.os.Bundle; 4 import android.view.View; 5 import android.widget.Button; 6 import android.widget.EditText; 7 import android.widget.TextView; 8 import android.widget.Toast; 9 import androidx.appcompat.app.AppCompatActivity; 10 11 public class MainActivity extends AppCompatActivity { 12 13 private EditText etBoy; 14 private EditText etKilo; 15 private Button btnHesapla; 16 private TextView tvSonuc; 17 18 @Override 19 protected void onCreate(Bundle savedInstanceState) { 20 super.onCreate(savedInstanceState); 21 setContentView(R.layout.activity_main); 22 23 // XML'deki arayüz elemanlarını Java değişkenlerine bağlıyoruz 24 etBoy = findViewById(R.id.etBoy); 25 etKilo = findViewById(R.id.etKilo); 26 btnHesapla = findViewById(R.id.btnHesapla); 27 tvSonuc = findViewById(R.id.tvSonuc); 28 29 // Butona tıklandığında hesaplama mantığı çalışır 30 btnHesapla.setOnClickListener(new View.OnClickListener() { 31 @Override 32 public void onClick(View v) { 33 String boyStr = etBoy.getText().toString().trim(); 34 String kiloStr = etKilo.getText().toString().trim(); 35 36 // Boş alan kontrolü 37 if (boyStr.isEmpty() || kiloStr.isEmpty()) { 38 Toast.makeText(MainActivity.this, "Lütfen boy ve kilo değerlerini giriniz!", Toast.LENGTH_SHORT).show(); 39 return; 40 } 41 42 try { 43 float boyCm = Float.parseFloat(boyStr); 44 float kilo = Float.parseFloat(kiloStr); 45 46 // cm'yi metreye çeviriyoruz: boy (m) = boy (cm) / 100 47 float boyMetre = boyCm / 100.0f; 48 float vki = kilo / (boyMetre * boyMetre); 49 50 tvSonuc.setText(String.format("VKİ Sonucunuz: %.2f", vki)); 51 } catch (NumberFormatException e) { 52 Toast.makeText(MainActivity.this, "Geçersiz bir sayı girdiniz!", Toast.LENGTH_SHORT).show(); 53 } 54 } 55 }); 56 } 57 }
Uygulamanın ekran görüntüsü:

Bu eski yöntemin zorlukları:
findViewById Hamallığı: Her arayüz elemanına ayrı bir ID verip onu tek tek koda bağlamak gerekir. Bir ID ismi değişirse uygulama çalışma anında çöker (NullPointerException).Yeni ve Modern Yaklaşım: Jetpack Compose
Jetpack Compose ise bu karmaşayı tamamen ortadan kaldırdı. Artık XML dosyalarına hiç ihtiyaç kalmadı!
Compose ile arayüzü doğrudan sadece Kotlin kodları yazarak tasarlıyoruz. Buton, metin kutusu ve tüm sayfa düzeni tek bir Kotlin dosyasında fonksiyonlar hâlinde tanımlanıyor. Bir değer değiştiğinde ekranı elle güncellemeye gerek kalmıyor; Compose durumu (state) otomatik olarak fark edip ekranı kendisi yeniden çiziyor.
Kısacası:
| XML (Eski) | Compose (Yeni) |
|---|---|
| Ayrı bir tasarım dosyası gerekir (XML) | Her şey saf Kotlin kodunda |
findViewById veya ViewBinding gerekir |
Doğrudan Composable fonksiyon çağrısı |
| İki dili eşzamanlı yönetmek gerekir | Tek dil (Kotlin), tek dosya |
| Durum değişiklikleri elle kodlanır | Durum (State) değişince ekran otomatik çizilir |
| Kod satırı fazla ve hata ihtimali yüksek | Çok daha az kod, temiz ve modern mimari |
Bu eğitimde Compose kullanacağız çünkü modern Android geliştirmenin geleceği ve Google'ın resmî olarak önerdiği standart yol Compose'dur.
Jetpack Compose ile yeni bir projeye başlarken:
Empty Activity ile Empty Views Activity'yi karıştırmayın!
Az önce XML ve Java örneğimizde kullandığımız Empty Views Activity, eski usul XML tabanlı arayüz dosyaları (activity_main.xml) üretir. Jetpack Compose ile modern şekilde geliştirmek için mutlaka yanında Compose logosu olan standart Empty Activity şablonunu seçmelisiniz.
Projeniz açıldığında Android Studio otomatik olarak MainActivity.kt dosyasını karşınıza getirir.
MainActivity.kt:
0 1 package com.example.vkihesaplayici 2 3 import android.os.Bundle 4 import androidx.activity.ComponentActivity 5 import androidx.activity.compose.setContent 6 import androidx.activity.enableEdgeToEdge 7 import androidx.compose.foundation.layout.fillMaxSize 8 import androidx.compose.foundation.layout.padding 9 import androidx.compose.material3.Scaffold 10 import androidx.compose.material3.Text 11 import androidx.compose.runtime.Composable 12 import androidx.compose.ui.Modifier 13 import androidx.compose.ui.tooling.preview.Preview 14 import com.example.vkihesaplayici.ui.theme.VkiHesaplayiciTheme 15 16 // Uygulamanın ana kapısıdır, ekran açıldığında ilk çalışan yerdir 17 class MainActivity : ComponentActivity() { 18 override fun onCreate(savedInstanceState: Bundle?) { 19 super.onCreate(savedInstanceState) 20 21 // Ekranın bildirim çubuğu dahil uçtan uca kaplanmasını sağlar 22 enableEdgeToEdge() 23 24 // Arayüzün Compose ile çizileceğini belirtir (XML'deki setContentView yerine geçer) 25 setContent { 26 // Projenin tema ve renk ayarlarını uygular 27 VkiHesaplayiciTheme { 28 // Sayfanın temel iskeletini oluşturur 29 Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> 30 // Varsayılan örnek metin fonksiyonunu çağırır 31 Greeting( 32 name = "Android", 33 modifier = Modifier.padding(innerPadding) 34 ) 35 } 36 } 37 } 38 } 39 } 40 41 // Android Studio'nun örnek olarak sunduğu Composable arayüz fonksiyonu 42 @Composable 43 fun Greeting(name: String, modifier: Modifier = Modifier) { 44 Text( 45 text = "Hello $name!", 46 modifier = modifier 47 ) 48 } 49 50 // Kodu cihaza yüklemeden yan panelde canlı önizleme yapmayı sağlar 51 @Preview(showBackground = true) 52 @Composable 53 fun GreetingPreview() { 54 VkiHesaplayiciTheme { 55 Greeting("Android") 56 } 57 }
Proje ilk açıldığında Android Studio bize hazır bir örnek olarak Greeting fonksiyonunu sunar:
0 1 @Composable 2 fun Greeting(name: String, modifier: Modifier = Modifier) { 3 Text( 4 text = "Hello $name!", 5 modifier = modifier 6 ) 7 }
Bu kod parçasının çalışma mantığı şöyledir:
@Composable: Fonksiyonun arayüz çizen bir bileşen olduğunu belirtir.name: String: Fonksiyona dışarıdan bir metin ("Android") parametresi gönderilir.modifier: Modifier = Modifier: Bileşenin dışarıdan boyut, dolgu (padding) veya konum gibi özellikler alabilmesini sağlar. Eğer çağıran yer bir modifier vermezse varsayılan boş Modifier kullanılır.Text(...): Compose'un ekrana yazı basmak için kullandığı temel arayüz elemanıdır (XML'deki TextView'ın karşılığıdır). Gönderilen name değerini alıp ekrana "Hello Android!" yazısını basar.MainActivity içerisindeki setContent bloğu bu fonksiyonu çağırır, en alttaki @Preview fonksiyonu ise Android Studio'nun sağ panelinde (Split/Design modunda) emülatörü başlatmaya gerek kalmadan bu yazının nasıl göründüğünü anlık olarak gösterir.
Şimdi varsayılan olarak gelen Greeting fonksiyonunu temizleyip yerine kendi @Composable fonksiyonumuzu, setContent çağrımızı ve yan panelde canlı önizleme yapmamızı sağlayan @Preview fonksiyonumuzu ekliyoruz:
MainActivity.kt (İlk Composable Kodumuz Eklenmiş Hâli):
0 1 package com.example.vkihesaplayici 2 3 import android.os.Bundle 4 import androidx.activity.ComponentActivity 5 import androidx.activity.compose.setContent 6 import androidx.activity.enableEdgeToEdge 7 import androidx.compose.foundation.layout.fillMaxSize 8 import androidx.compose.foundation.layout.padding 9 import androidx.compose.material3.Scaffold 10 import androidx.compose.material3.Text 11 import androidx.compose.runtime.Composable 12 import androidx.compose.ui.Modifier 13 import androidx.compose.ui.tooling.preview.Preview 14 import com.example.vkihesaplayici.ui.theme.VkiHesaplayiciTheme 15 16 class MainActivity : ComponentActivity() { 17 override fun onCreate(savedInstanceState: Bundle?) { 18 super.onCreate(savedInstanceState) 19 enableEdgeToEdge() 20 setContent { 21 VkiHesaplayiciTheme { 22 Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> 23 VkiHesaplayiciEkrani(modifier = Modifier.padding(innerPadding)) 24 } 25 } 26 } 27 } 28 } 29 30 @Composable 31 fun VkiHesaplayiciEkrani(modifier: Modifier = Modifier) { 32 // Buraya kodlar yazılacak 33 } 34 35 @Preview(showBackground = true) 36 @Composable 37 fun VkiHesaplayiciEkraniPreview() { 38 VkiHesaplayiciTheme { 39 VkiHesaplayiciEkrani() 40 } 41 }
Modifier Nedir ve Neden
modifier: Modifier = ModifierYazıyoruz?
Compose dünyasında Modifier (Değiştirici), arayüz bileşenlerinin boyutunu, dış/iç boşluklarını (padding), arka plan rengini, tıklama davranışını veya hizalamasını belirleyen en temel yapıdır. CSS veya XML'dekilayout_width,margin,paddinggibi niteliklerin tek bir çatı altında toplanmış modern hâlidir.
Neden parametre olarak veriyoruz? (
modifier: Modifier = Modifier)
Compose'da en iyi pratik (best practice), yazdığınız her@Composablefonksiyonuna birmodifierparametresi tanımlamaktır. Böylece o fonksiyonu dışarıdan çağıran üst bileşen (örneğinScaffold), fonksiyona dışarıdan istediği boşluğu veya boyutu giydirebilir.= Modifieryazarak varsayılan (boş) bir değer atarız; bu sayede@Previewiçinde olduğu gibi istersek hiç parametre vermeden de çağırabiliriz.
innerPaddingHatası Neydi?Scaffoldbileşeni; telefonun çentik, saat/pil durum çubuğu ve alt navigasyon tuşları gibi sistem alanlarının içeriğin üzerine taşmaması için bize otomatik birinnerPadding(güvenli boşluk) hesaplar. EğerinnerPadding'i içeriye iletmezsek Android Studio "innerPadding is not used" uyarısı verir. Bu yüzdenVkiHesaplayiciEkrani(modifier = Modifier.padding(innerPadding))diyerek bu güvenli alanı fonksiyonumuza aktarıyoruz.
Peki @Composable etiketi (annotation) nedir?
@Composable yazıp fonksiyonun hemen üstüne koyduğumuzda, Compose'a "Bu fonksiyon bir arayüz parçası çizecek, sen bununla ilgilen" demiş oluyoruz. Bunu fonksiyonunuza zarif bir rozet takmak gibi düşünebilirsiniz. Bu rozet sayesinde, içerisine yazacağımız buton, metin gibi arayüz elemanları ekranda görünebilir hâle gelir.
Şimdilik fonksiyonumuz boş. Ekranımız da boş. Birazdan içerisini boy, kilo ve hesaplama elemanlarıyla canlandıracağız!
Uygulamamızda metin kutuları, etiketler ve bir buton olacak. Bunların ekranda alt alta durmasını istiyoruz: önce başlık, altında boy kutusu; onun altında kilo kutusu; en altta da hesapla butonu.
İşte tam burada Column devreye girer.
Column nedir?
Column, adından da anlaşılacağı gibi bir sütun yapısıdır. İçine koyduğun tüm elemanları dikey, yani alt alta dizer. Marketten alışveriş listesini düşün; her madde bir satırdır ve aşağı doğru sıralanır. Column tam olarak bunu yapar.
Kodumuza Column'u şöyle ekliyoruz:
0 1 @Composable 2 fun VkiHesaplayiciEkrani(modifier: Modifier = Modifier) { 3 Column( 4 modifier = modifier 5 .fillMaxSize() 6 .padding(16.dp) 7 ) { 8 // Arayüz elemanlarımız buraya gelecek 9 } 10 }
Burada gördüğün Modifier kısmı "düzenleyici" demektir. fillMaxSize() sütunun tüm ekranı kaplamasını, padding(16.dp) ise kenarlardan 16 dp boşluk bırakılmasını sağlar. Böylece elemanlar ekranın kenarına yapışmaz, ferah bir görünüm olur.
Kodda Column veya dp Kırmızı mı Yanıyor? (Otomatik Import Ayarı)
Kodu ilk yazdığınızda Column veya .dp ifadesinin altı kırmızı çizilebilir. Bunun sebebi, o bileşenlerin kütüphanelerinin henüz dosyanın tepesine içe aktarılmamış (import) olmasıdır. Normalde kırmızı yanan kelimenin üzerine gelip Alt + Enter tuşuna basarak import edebilirsiniz.
Ancak Android Studio'da bu süreci tamamen otomatiğe bağlamak ve bir daha kütüphane ekleme zahmetiyle uğraşmamak için şu adımları izleyebilirsiniz:
Bu ayarları aktif ettikten sonra; Column, dp, Modifier veya Button gibi tek bir alternatifi olan sınıfları yazdığınız anda, hiçbir kısayola basmanıza gerek kalmadan ilgili kütüphane dosyanın en üstüne arka planda anında eklenecektir.
Şimdi sıra geldi en eğlenceli kısma: arayüzü gerçek elemanlarla doldurmaya!
Kullanıcıdan boy ve kilo bilgilerini almak için OutlinedTextField (metin kutusu), başlıklar için Text (metin), hesaplamayı tetiklemek için de Button (buton) kullanacağız.
Şimdilik değişken tanımlamalarına veya hesaplama işlemlerine girmeden doğrudan arayüzün iskeletini kuralım:
0 1 @Composable 2 fun VkiHesaplayiciEkrani(modifier: Modifier = Modifier) { 3 Column( 4 modifier = modifier 5 .fillMaxSize() 6 .padding(16.dp) 7 ) { 8 Text(text = "VKİ Hesaplayıcı") 9 10 Text(text = "Boyunuz (cm)") 11 OutlinedTextField( 12 value = "", 13 onValueChange = { }, 14 label = { Text("Örn: 175") } 15 ) 16 17 Text(text = "Kilonuz (kg)") 18 OutlinedTextField( 19 value = "", 20 onValueChange = { }, 21 label = { Text("Örn: 70") } 22 ) 23 24 Button( 25 onClick = { 26 // Hesaplama daha sonra yapılacak 27 } 28 ) { 29 Text(text = "VKİ Hesapla") 30 } 31 } 32 }
Bileşenlerin ne anlama geldiğine birlikte bakalım:
Text(...) — Ekrana yazı yazar. Başlığı ve alan etiketlerini bununla koyduk.OutlinedTextField(...) — Kullanıcının yazı yazabileceği çerçeveli bir metin kutusudur:value: Kutuya o an yazılı olan metin.onValueChange: Kullanıcı her tuşa bastığında çalışan fonksiyon bloğu.label: Kutunun içinde "Örn: 175" gibi soluk bir ipucu gösterir.Button(onClick = { ... }) — Tıklanabilir bir buton oluşturur. İçindeki boş kısım, ilerleyen adımlarda hesaplama mantığını ekleyeceğimiz yerdir.Text ve OutlinedTextField Otomatik Eklendi Ama Button Neden Kırmızı Yandı?
Az önce açtığımız otomatik import ayarı sayesinde Text ve OutlinedTextField gibi bileşenler kod yazılırken arka planda kendiliğinden dosyanın en üstüne eklendi. Ancak Button yazdığınızda altının kırmızı yandığını fark edeceksiniz.
Bunun sebebi şudur:
Otomatik import özelliği yalnızca tek bir alternatif (çakışmayan) olduğunda arka planda çalışır. Android ekosisteminde Button ismini taşıyan birden fazla kütüphane bulunduğu için Android Studio hangisini kastettiğinizi bilemez ve hata yapmamak adına seçimi size bırakır.
Klavyeden Alt + Enter tuşlarına bastığınızda karşınıza çıkan seçeneklerin farkları şunlardır:
@Composable Button(...) (androidx.compose.material3): Jetpack Compose'un Material 3 tasarımı için sunduğu modern arayüz butonudur. Compose kullandığınız için seçmeniz gereken doğru seçenek budur.Button (of android.widget): Klasik Android View (XML) sistemine ait eski tip buton sınıfıdır. Compose kodları içerisinde çalışmaz ve hata verir.Button (in androidx.compose.ui.semantics.Role.Companion): Ekrana çizilen görsel bir buton değil, ekran okuyucular (erişilebilirlik servisleri) için bir nesnenin "buton rolünde" olduğunu belirten bir etikettir.Dolayısıyla açılan listeden @Composable Button(...) (androidx.compose.material3) seçeneğini seçtiğinizde kırmızı çizgi anında kaybolacaktır.
Tasarımımızın ekranda nasıl durduğunu görmek için iki harika seçeneğimiz var:
Android Studio Design (Split) Modu:
Kod editörünün sağ üst köşesinde yer alan Split veya Design butonuna tıklayarak @Preview fonksiyonumuz sayesinde emülatör başlatmadan arayüzün anlık önizlemesini görebilirsiniz. Kodda yaptığınız stil değişiklikleri burada anında güncellenir.
Android Sanal Cihazı (AVD / Emulator) ile Çalıştırma:
Uygulamayı gerçek bir telefon ortamında test etmek için üst araç çubuğundaki yeşil Run ('app') (Shift + F10) butonuna basarak sanal cihazda çalıştırabilirsiniz.
Önerilen AVD (Android Sanal Cihaz) Ayarları:
Akıcı, kararlı ve hızlı bir test deneyimi için sanal cihaz oluştururken şu yapılandırmayı tercih edebilirsiniz:
Uygulamamızın Emülatördeki İlk Hâli:

İlk tasarımımız çalışıyordu ancak elemanlar ekranın en üstüne sıkışmıştı, butonumuz küçüktü ve kullanıcı metin kutusuna dokunduğunda normal harf klavyesi açılıyordu. Gerçek ve modern bir kullanıcı deneyimi için tasarımımıza harika dokunuşlar ekliyoruz!
İşte iyileştirilmiş yeni ekran kodumuz:
0 1 @Composable 2 fun VkiHesaplayiciEkrani(modifier: Modifier = Modifier) { 3 // Elemanları alt alta dizmek için Column kullanıyoruz 4 Column( 5 modifier = modifier 6 .fillMaxSize() 7 .padding(16.dp), 8 horizontalAlignment = Alignment.CenterHorizontally, 9 verticalArrangement = Arrangement.Center 10 ) { 11 Text( 12 text = "VKİ Hesaplayıcı", 13 fontSize = 24.sp, 14 modifier = Modifier.padding(bottom = 32.dp) 15 ) 16 17 // Boy girişi için metin kutusu 18 OutlinedTextField( 19 value = "", 20 onValueChange = { }, 21 label = { Text("Boy (cm) (örn: 175)") }, 22 keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), 23 modifier = Modifier.fillMaxWidth() 24 ) 25 26 Spacer(modifier = Modifier.height(16.dp)) 27 28 // Kilo girişi için metin kutusu 29 OutlinedTextField( 30 value = "", 31 onValueChange = { }, 32 label = { Text("Kilo (örn: 70)") }, 33 keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), 34 modifier = Modifier.fillMaxWidth() 35 ) 36 37 Spacer(modifier = Modifier.height(32.dp)) 38 39 // Hesapla Butonu 40 Button( 41 onClick = { 42 // Şimdilik burası boş 43 }, 44 modifier = Modifier.fillMaxWidth().height(50.dp) 45 ) { 46 Text(text = "Hesapla", fontSize = 18.sp) 47 } 48 } 49 }
Ekranı Ortaya Hizalama (Alignment ve Arrangement):
horizontalAlignment = Alignment.CenterHorizontally: İçindeki tüm bileşenleri yatay eksende tam ortaya hizaladı.verticalArrangement = Arrangement.Center: Tüm içeriği ekranın en üstüne yapışmaktan kurtarıp telefon ekranının tam ortasına topladı.Daha Büyük Başlık ve Alt Boşluk:
fontSize = 24.sp vererek daha belirgin hâle getirdik ve altındaki kutulara yapışmaması için padding(bottom = 32.dp) ekledik.Sayısal Klavye Desteği (KeyboardOptions):
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number) ekleyerek kullanıcı kutuya tıkladığında harf klavyesi yerine doğrudan sayısal klavyenin açılmasını sağladık. Böylece kullanıcı harf girmeye çalışamaz, daha hızlı ve hatasız giriş yapar.Kutuların Genişletilmesi (fillMaxWidth):
modifier = Modifier.fillMaxWidth() sayesinde metin kutularının ekran genişliğini zarif bir şekilde doldurmasını sağladık.Aralara Boşluk Bırakma (Spacer):
Spacer(modifier = Modifier.height(...)) bileşenleri koyduk. Bu sayede kutular ve buton arasında nefes alan, düzenli bir boşluk oluştu.Geniş ve Belirgin Buton:
fillMaxWidth() ve height(50.dp) vererek tek elle kolayca basılabilecek, modern ve şık bir buton tasarımı elde ettik.
Arayüzümüz artık tam istediğimiz gibi görünüyor! Ancak kutulara yazı yazmaya çalıştığınızda henüz hiçbir şey değişmediğini fark edeceksiniz. Neden? Çünkü Compose'da ekrandaki her şey bir duruma (state) bağlıdır.
Kullanıcının boyunu, kilosunu ve hesaplanan sonucu saklayacak değişkenleri tanımlamamız gerekir. İşte burada state (durum) kavramı devreye girer.
Fonksiyonumuzun başına şu kodları ekliyoruz:
0 1 @Composable 2 fun VkiHesaplayiciEkrani(modifier: Modifier = Modifier) { 3 4 var boy by remember { mutableStateOf("") } 5 var kilo by remember { mutableStateOf("") } 6 var sonuc by remember { mutableStateOf("") } 7 8 // Kodların devamı ...
Şimdi bu üç anahtar yapıyı tek tek inceleyelim:
mutableStateOf — Değiştirilebilir Durum:
Kutunun içindeki değerin değişebileceğini belirtir. Kullanıcı "175" yazdığında bu değer güncellenir. Değer her değiştiğinde Compose bunu otomatik olarak fark eder ve ekranı yeniden çizer (Recomposition).
remember — Değeri Hafızada Tut:
Compose ekranı yeniden çizerken değişkenleri sıfırlayabilir. remember, "bu kutunun içindeki değeri ekran her yenilendiğinde hafızada sakla, unutma" der.
by — Yetki Devri (Delegation):
Normalde remember { mutableStateOf("") } yazdığımızda değere erişmek için .value yazmak gerekir. Başına by koyduğumuzda Kotlin bu yetkiyi devreder ve doğrudan boy yazarak değeri okuyup değiştirebilmemizi sağlar. Kodumuzu çok daha temiz ve kısa hâle getirir.
by Yazınca Neden İki Kez Import İstiyor ve Hâlâ Kırmızı Yanıyor?by anahtar kelimesini ilk yazdığınızda altı kırmızı yanar. Üzerine gelip tıkladığınızda önce:
Import operator 'State.getValue' yazar. Tıklarsınız ancak by hâlâ kırmızı yanmaya devam eder! Import operator 'MutableState.setValue' önerisi çıkar.Bu durumun sebebi:mutableStateOf nesnesini val (sabit) yerine var (değiştirilebilir) olarak tanımlamanızdır. Kotlin'de by anahtar kelimesi (delegation) ile bir var değişkeni oluşturduğunuzda, arka planda sistemin bu değeri hem okuyabilmesi (get) hem de değiştirebilmesi (set) gerekir.
Android Studio'nun size sırasıyla iki farklı import sunmasının nedeni şudur:
getValue fonksiyonunu import eder.var olduğu için değerin güncellenebilmesi de gerekir. Bu yüzden hâlâ kırmızı yanar ve ikinci kez tıkladığınızda yazma/değiştirme işlemi için gereken setValue fonksiyonunu import eder.Her iki uyarıya da sırasıyla tıklayıp (Alt + Enter) import işlemlerini onayladığınızda kırmızı hata tamamen kaybolacaktır.
Değişkenlerimizi tanımladığımıza göre artık metin kutularını bu değişkenlere bağlayabilir, butona tıklandığında boy ve kilo değerlerini alarak hesaplamayı yapabiliriz.
Önceki tasarımımızda metin kutularımızın değerlerini sabit (value = "") ve boş (onValueChange = { }) bırakmıştık. Tanımladığımız boy ve kilo değişkenlerinin arayüzle etkileşime girebilmesi için OutlinedTextField bileşenlerinin value ve onValueChange değerlerini bu değişkenlerle değiştiriyoruz:
value = boy (ve kilo): Kutunun ekranda kullanıcının girdiği güncel değişken değerini göstermesini sağlar.onValueChange = { boy = it } (ve kilo = it): Kullanıcı klavyeden her karakter girdiğinde veya sildiğinde (it), bu yeni değeri değişkene atayarak durumun (State) anında güncellenmesini sağlar.0 1 // önceki kodlar 2 3 // Boy girişi 4 OutlinedTextField( 5 value = boy, 6 onValueChange = { boy = it }, 7 label = { Text("Boy (cm) (örn: 175)") }, 8 keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), 9 modifier = Modifier.fillMaxWidth() 10 ) 11 12 Spacer(modifier = Modifier.height(16.dp)) 13 14 // Kilo girişi 15 OutlinedTextField( 16 value = kilo, 17 onValueChange = { kilo = it }, 18 label = { Text("Kilo (örn: 70)") }, 19 keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), 20 modifier = Modifier.fillMaxWidth() 21 ) 22 23 // sonraki kodlar
Vücut Kitle İndeksi standart formülü:
VKİ = Kilo (kg) / Boy (m)²
Burada dikkat etmemiz gereken iki önemli nokta vardır:
175) gireceği için formüle koymadan önce metreye çeviriyoruz: val boyMetre = boyCm / 100ftoFloatOrNull() ile güvenli sayı dönüşümü ve kontrol ekliyoruz.onClick OlayıButona tıklandığında çalışacak hesaplama bloğu:
0 1 val boyCm = boy.toFloatOrNull() 2 val kiloSayi = kilo.toFloatOrNull() 3 4 if (boyCm != null && kiloSayi != null && boyCm > 0f) { 5 val boyMetre = boyCm / 100f 6 val vki = kiloSayi / (boyMetre * boyMetre) 7 8 val durum = when { 9 vki < 18.5f -> "Zayıf" 10 vki < 25f -> "Normal Kilolu" 11 vki < 30f -> "Fazla Kilolu" 12 else -> "Obez" 13 } 14 15 sonuc = String.format("VKİ: %.2f (%s)", vki, durum) 16 } else { 17 sonuc = "Lütfen geçerli değerler giriniz!" 18 }
Hesaplama sonucunu sonuc değişkenine aktardık; ancak bu sonucu ekranda kullanıcının görebilmesi için bir alana ihtiyacımız vardır. Butonumuzun hemen altına, yalnızca sonuç boş olmadığında (sonuc.isNotEmpty()) ekranda belirecek şu Text alanını ekliyoruz:
0 1 // Sonucun gösterileceği alan 2 if (sonuc.isNotEmpty()) { 3 Spacer(modifier = Modifier.height(24.dp)) 4 Text( 5 text = sonuc, 6 fontSize = 20.sp, 7 fontWeight = FontWeight.Bold 8 ) 9 }
Uygulamamız artık tam anlamıyla çalışıyor; ancak tasarımımızı daha profesyonel ve estetik kılmak için "VKİ Hesaplayıcı" başlığının hemen üzerine bir logo görseli ekleyebiliriz.
Kullanacağımız logo görselimiz:

app > res > drawable klasörüne tıklayıp Ctrl + V ile yapıştırın.img.png adıyla gelecektir; görselin üzerine sağ tıklayıp Refactor > Rename (veya Shift + F6) diyerek adını vki_logo.png olarak değiştirin.Kaynak İsimlendirme Kuralı:
Android'in res/drawable dizinine eklenen dosya adları yalnızca küçük harf (a-z), rakam (0-9) ve alt çizgi (_) barındırabilir. Büyük harf, Türkçe karakter (ı, ş, ğ vb.) veya boşluk içerirse derleme hatası verir.
Image Bileşeni ile Logoyu GöstermeJetpack Compose'da görsel göstermek için Image bileşeninden yararlanırız.
Column içeriğimizin en başına, Text("VKİ Hesaplayıcı") başlığının hemen üstüne logomuzu ekliyoruz:
0 1 // Logo Görseli 2 Image( 3 painter = painterResource(id = R.drawable.vki_logo), 4 contentDescription = "VKİ Logo", 5 modifier = Modifier.size(120.dp) 6 ) 7 8 Spacer(modifier = Modifier.height(16.dp)) 9 10 Text( 11 text = "VKİ Hesaplayıcı", 12 fontSize = 24.sp, 13 modifier = Modifier.padding(bottom = 32.dp) 14 )
painter = painterResource(id = R.drawable.vki_logo): drawable klasörümüzdeki görsel kaynağını yükler.contentDescription = "VKİ Logo": Ekran okuyucular (TalkBack) için görselin ne olduğunu ifade eden erişilebilirlik açıklamasıdır.modifier = Modifier.size(120.dp): Logonun ekrandaki genişlik ve yüksekliğini 120 dp olarak sınırlar.Gerekli Importlar:
Eğer Image veya painterResource fonksiyonlarının altı kırmızı yanarsa şu paketleri import ettiğinizden emin olun (Alt + Enter):
0 1 import androidx.compose.foundation.Image 2 import androidx.compose.ui.res.painterResource
Uygulamamız çalıştığında telefonun ana ekranında ve uygulama çekmecesinde varsayılan olarak Android'in standart yeşil robot simgesi yer alır. Uygulamamıza profesyonel ve özgün bir görünüm kazandırmak için vki_logo.png görselimizi uygulamanın başlatıcı ikonu (Launcher Icon) olarak ayarlayacağız.
Android Studio, farklı ekran çözünürlükleri için tüm ikon boyutlarını otomatik üreten harika bir Image Asset aracına sahiptir.
Image Asset Aracını Açın:
Android Studio'da sol tarafta bulunan Project görünümünde app veya res klasörüne sağ tıklayın. Menüden New > Image Asset seçeneğine tıklayın.
İkon Tipini Belirleyin:
Launcher Icons (Adaptive and Legacy) (Varsayılan seçim olarak bırakın).ic_launcher (Varsayılan ismini değiştirmeyin; böylece mevcut simgelerin üzerine otomatik yazılır).Ön Katmanı (Foreground Layer) Ayarlayın:
vki_logo.png görselini seçin.Arka Plan Katmanını (Background Layer) Ayarlayın:
#FFFFFF veya logonuzla uyumlu bir renk) belirleyin.İkonları Projeye Kaydedin:
Android Studio bu işlemle birlikte tüm ekran yoğunluklarına uygun (mipmap-mdpi, hdpi, xhdpi, xxhdpi, xxxhdpi) ikon setlerini ve modern Android sürümleri için uyarlanabilir (Adaptive Icon) dosyalarını otomatik oluşturur. AndroidManifest.xml dosyasında herhangi bir manuel değişiklik yapmanıza gerek kalmaz.
Uygulamayı emülatörde veya gerçek cihazınızda yeniden çalıştırdığınızda (Shift + F10), ana ekranda artık uygulamanızın kendi özel logosuyla yer aldığını görebilirsiniz.
Tebrikler! Jetpack Compose ile modern, reaktif ve hatasız çalışan ilk Android uygulamanızı başarıyla tamamladınız. Artık kullanıcı değerleri girip Hesapla butonuna bastığında sonuç anında ve şık bir biçimde ekranda belirecektir.
Uygulamamızın Tamamlanmış Hâli:

Okul Zil Programı v3 - Otomatik Saat Dağıtımı ve 100'den Fazla Zil SesiOkullarda ders ve teneffüs saatlerini otomatik olarak yöneten, kullanımı kolay ve gelişmiş özelliklere sahip okul zil programı hakkında detaylar.
Android silinmeyen uygulamaları kaldırma [rootsuz ve bilgisayarsız]Android cihazınızdaki silinmeyen sistem uygulamalarından (bloatware) kurtulmanın en kolay yolu. Root atmadan ve bilgisayar kullanmadan Shizuku ve Canta uygulamaları ile gereksiz uygulamaları nasıl kaldırabileceğinizi adım adım öğrenin.
Intel i3, i5, i7 ve i9 İşlemciler Arasındaki Fark Nedir?Intel Core i3, i5, i7 ve i9 işlemciler arasındaki temel farkları inceliyoruz. Hangi işlemcinin ihtiyaçlarınıza en uygun olduğunu, fiyat, performans ve kullanım senaryolarına göre karşılaştırarak öğrenin.