Kotlin Multiplatform (KMP), iş mantığınızı bir kez yazıp hem Android hem de iOS tarafında paylaşmanızı sağlayan, JetBrains öncülüğünde geliştirilen bir yaklaşım. Flutter veya React Native’in aksine, KMP arayüzü değil, iş mantığını paylaşır. Bu farkın neden önemli olduğunu ve sıfırdan bir KMP projesi kurarken nelere dikkat etmeniz gerektiğini bu yazıda ele alıyoruz.
KMP neden tercih edilir?
Tam bir cross-platform çözümün aksine, KMP’de arayüz katmanı platforma özgü kalır: Android tarafında Jetpack Compose, iOS tarafında SwiftUI (veya UIKit) kullanmaya devam edersiniz. Paylaşılan olan kısım; ağ istekleri, veri modelleri, iş kuralları ve genellikle veritabanı erişimi gibi platformdan bağımsız kod. Bunun pratik sonucu şu:
- Her platform kendi doğal kullanıcı deneyimini (UX) korur; kullanıcı arayüz kütüphanesinden ödün vermezsiniz.
- İş mantığındaki bir hatayı bir kez düzeltirsiniz, iki platforma da yansır.
- Ekip, platforma özgü uzmanlığını (Android/iOS geliştiricileri) korurken ortak modül üzerinde birlikte çalışabilir.
Bunun maliyeti de var: Kotlin/Native’in derleme süreleri ve bellek yönetimi modeli, saf Android geliştirmeye göre ek bir öğrenme eğrisi getirir; ekibinizin bu yatırımı yapmaya değer görüp görmediğini baştan değerlendirin.
Proje yapısı
Tipik bir KMP projesi üç ana modülden oluşur: platformdan bağımsız ortak kodun yaşadığı shared modülü, Android uygulama modülü ve iOS uygulaması (genellikle ayrı bir Xcode projesi olarak). Ortak modül, kendi içinde üç kaynak setine (source set) ayrılır:
shared/
├── src/
│ ├── commonMain/ # Tüm platformlarda paylaşılan kod
│ ├── androidMain/ # Yalnızca Android'e özgü uygulamalar
│ └── iosMain/ # Yalnızca iOS'a özgü uygulamalar
Basit bir ortak sınıf şöyle görünür:
// shared/src/commonMain/kotlin/Greeting.kt
class Greeting {
fun merhaba(): String {
return "Merhaba, Kotlin Multiplatform!"
}
}
Bu sınıf hem Android tarafında doğrudan bir Kotlin bağımlılığı gibi, hem de iOS tarafında Kotlin/Native’in ürettiği bir framework üzerinden Swift’ten çağrılabilir.
Platforma özgü kod nasıl yazılır?
Bazı işlemler (dosya sistemi erişimi, tarih/saat formatlama, güvenli depolama gibi) platforma özgüdür ve her işletim sisteminde farklı API’ler gerektirir. KMP bunun için expect/actual mekanizmasını sunar: ortak modülde bir fonksiyonun ya da sınıfın imzasını expect ile tanımlarsınız, her platform modülünde ise actual ile gerçek uygulamasını yazarsınız.
// commonMain
expect fun platformAdi(): String
// androidMain
actual fun platformAdi(): String = "Android"
// iosMain
actual fun platformAdi(): String = "iOS"
Derleyici, ortak modüldeki her expect bildirimi için her platform modülünde karşılık gelen bir actual bildirimi olup olmadığını kontrol eder; eksikse derleme hata verir. Bu da platform kapsamının unutulmasını derleme zamanında yakalayan güçlü bir güvenlik ağı sağlar.
Ağ, veritabanı ve bağımlılık enjeksiyonu
Gerçek bir uygulamada, iş mantığının büyük kısmı üç alanda toplanır ve bu alanlar için olgunlaşmış, çok platformlu kütüphaneler mevcut:
- Ağ istekleri için Ktor Client, hem Android hem iOS’ta aynı API ile HTTP istekleri yapmanızı sağlar.
- Yerel veritabanı için SQLDelight, SQL şemanızdan tip güvenli Kotlin kodu üretir ve her iki platformda da aynı sorguları çalıştırır.
- Bağımlılık enjeksiyonu için Koin, saf Kotlin ile yazıldığından KMP projelerinde ek bir platform köprüsüne ihtiyaç duymadan kullanılabilir.
// commonMain: Ktor ile basit bir GET isteği
suspend fun kullaniciGetir(id: String): Kullanici {
return httpClient.get("https://api.ornek.com/kullanicilar/$id").body()
}
Bu üç kütüphaneyi birlikte kullanan bir mimaride, commonMain içindeki bir Repository sınıfı hem ağ hem veritabanı erişimini soyutlar; ViewModel (Android) veya benzeri bir durum yöneticisi de bu repository’yi ortak modülden çağırır.
Test etme
KMP’nin bir diğer avantajı, ortak modüldeki iş mantığını platformdan bağımsız test edebilmenizdir:
// commonTest
class GreetingTest {
@Test
fun merhabaMesajiDogru() {
assertEquals("Merhaba, Kotlin Multiplatform!", Greeting().merhaba())
}
}
Bu testler commonTest kaynak setinde yazılır ve hem JVM (Android) hem de native (iOS) hedefinde çalıştırılabilir. Aynı test kodu, iki platformda da iş mantığınızın doğruluğunu garanti eder.
Sık karşılaşılan tuzaklar
En sık yapılan hatalardan biri, ortak modülde platforma özgü bir kütüphaneyi (örneğin Android’e özgü bir bağımlılık) doğrudan kullanmaya çalışmaktır; bu, projeyi derlenemez hâle getirir. Bir diğer önemli tuzak, iOS tarafında Kotlin/Native’in bellek yönetimi modelini göz ardı etmek: özellikle bir veritabanı katmanı (örneğin Realm) kullanan projelerde, yönetilen (managed) bir nesnenin birincil anahtarını (primary key) yeniden atamaya çalışmak veya iş parçacıkları (thread) arasında paylaşılan nesnelere dikkatsizce erişmek, iOS tarafında beklenmedik çökmelere yol açabilir. Bu tür hataları erken yakalamak için ortak modülün iOS hedefine karşı da düzenli olarak test çalıştırmak gerekir.
Bir diğer pratik detay: ortak modülden fırlatılan (throw edilen) bir istisna, Swift tarafında @Throws anotasyonuyla işaretlenmemişse Swift kodu bu istisnayı yakalayamaz ve uygulama doğrudan çöker. Kullanıcıya hatayı düzgün göstermek istediğiniz her fonksiyonda bu anotasyonu eklemeyi unutmayın.
Nereden başlamalı?
JetBrains’in resmi Kotlin Multiplatform proje şablonuyla (kotlinlang.org üzerindeki resmi dokümantasyon ve “Create your Kotlin Multiplatform app” rehberi) başlamak, gereksiz yapılandırma karmaşasından kaçınmanın en hızlı yolu. İlk projenizde tüm uygulamayı ortak modüle taşımaya çalışmak yerine, tek bir basit özelliği (örneğin bir API çağrısı ve onu işleyen veri modeli) ortak modüle taşıyarak başlamanızı öneririz; bu, ekibinizin KMP’nin sınırlarını ve avantajlarını küçük riskle öğrenmesini sağlar.
Sonuç
Kotlin Multiplatform, iki ayrı kod tabanı yönetmenin getirdiği yükü azaltmak isteyen ekipler için olgunlaşmış, üretimde kanıtlanmış bir seçenek hâline geldi. Arayüzü değil iş mantığını paylaşma felsefesi, tam cross-platform çözümlere göre daha az riskli bir orta yol sunuyor. Bir sonraki yazıda, KMP projesinde Jetpack Compose tarafında durum yönetimini nasıl kurguladığımızı ele alacağız.