Kotlin Multiplatform (KMP) ile 2026'da Cross-Platform Geliştirme: Compose Multiplatform + Ktor + SQLDelight + Shared ViewModel Tam Rehber

Yönetici
2026'da JetBrains’in Compose Multiplatform’u (Compose for iOS stabil, Web ve Desktop olgun) ve KMP ekosistemiyle artık “write once, run anywhere” gerçek anlamda mümkün. Bu rehberde Android + iOS için ortak bir Not Alma Uygulaması (shared business logic, shared UI, native navigation) yapacağız:
  • Shared ViewModel + StateFlow
  • SQLDelight ile cross-platform veritabanı
  • Ktor Client ile opsiyonel remote sync
  • Compose Multiplatform UI (Material 3)
  • Android + iOS native entegrasyonu
Gereksinimler
  • IntelliJ IDEA Ultimate / Android Studio 2025.x (KMP wizard destekli)
  • Xcode 17+ (iOS için)
  • Kotlin 2.3+, Compose Multiplatform 1.7+

Yeni proje oluştur: File → New → Project → Kotlin Multiplatform → Mobile Application → Compose Multiplatform

1. Proje Yapısı (Standart KMP 2026)​


Kod:
shared/                  # Ortak kod (business logic + UI)
├── src/
│   ├── commonMain/      # Her platformda çalışan kod
│   │   ├── kotlin/
│   │   │   ├── data/    # SQLDelight, models, repository
│   │   │   ├── domain/  # UseCase, entities
│   │   │   ├── ui/      # Compose screens, components
│   │   │   └── viewmodel/
│   ├── androidMain/     # Android spesifik
│   ├── iosMain/         # iOS spesifik
│   └── desktopMain/     # Opsiyonel
androidApp/              # Android uygulaması
iosApp/                  # Xcode projesi (SwiftUI wrapper)

2. SQLDelight ile Ortak Veritabanı​

build.gradle.kts (shared) → SQLDelight ekle:

Kod:
plugins {
id("app.cash.sqldelight") version "2.0.2"
}

sqldelight {
databases {
create("NoteDatabase") {
packageName = "com.example.notes.db"
dialect("app.cash.sqldelight:android-dialect:2.0.2") // android + native
}
}
}

shared/src/commonMain/sqldb/notes.sq

Kod:
CREATE TABLE Note (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
content TEXT NOT NULL,
createdAt INTEGER NOT NULL
);

insertNote:
INSERT INTO Note (title, content, createdAt)
VALUES (?, ?, ?);

getAllNotes:
SELECT * FROM Note ORDER BY createdAt DESC;

deleteNote:
DELETE FROM Note WHERE id = ?;

NoteDatabase.kt otomatik oluşur.

3. Shared Data Layer​


shared/src/commonMain/kotlin/data/NoteRepository.kt

Kod:
interface NoteRepository {
suspend fun insertNote(title: String, content: String)
fun getAllNotes(): Flow<List<Note>>
suspend fun deleteNote(id: Long)
}

class NoteRepositoryImpl(
private val db: NoteDatabase
) : NoteRepository {
override suspend fun insertNote(title: String, content: String) {
db.noteQueries.insertNote(title, content, Clock.System.now().toEpochMilliseconds())
}

override fun getAllNotes(): Flow<List<Note>> =
db.noteQueries.getAllNotes().asFlow().mapToList()

override suspend fun deleteNote(id: Long) {
db.noteQueries.deleteNote(id)
}
}

// Domain entity
data class Note(
val id: Long,
val title: String,
val content: String,
val createdAt: Instant
)

4. Shared ViewModel (Decompose veya pure Coroutines)​


shared/src/commonMain/kotlin/viewmodel/NotesViewModel.kt

Kod:
class NotesViewModel(
private val repository: NoteRepository
) : ViewModel() {  // androidx.lifecycle.ViewModel ortak

val notes: StateFlow<List<Note>> = repository.getAllNotes()
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())

fun addNote(title: String, content: String) {
viewModelScope.launch {
repository.insertNote(title, content)
}
}

fun deleteNote(note: Note) {
viewModelScope.launch {
repository.deleteNote(note.id)
}
}
}

5. Compose Multiplatform UI​

shared/src/commonMain/kotlin/ui/NotesScreen.kt


Kod:
@Composable
fun NotesScreen(
viewModel: NotesViewModel = remember { NotesViewModel(/* inject */) },
onNoteClick: (Note) -> Unit = {}
) {
val notes by viewModel.notes.collectAsStateWithLifecycle()

Column(modifier = Modifier.fillMaxSize().padding(16.dp)) {
Text("Notlarım", style = MaterialTheme.typography.headlineMedium)

LazyColumn {
items(notes, key = { it.id }) { note ->
Card(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp)
.clickable { onNoteClick(note) }
) {
Column(modifier = Modifier.padding(16.dp)) {
Text(note.title, style = MaterialTheme.typography.titleMedium)
Text(note.content.take(100) + "...", style = MaterialTheme.typography.bodyMedium)
Text(
note.createdAt.toLocalDateTime(TimeZone.currentSystemDefault())
.format(LocalDateTime.Format.ISO),
style = MaterialTheme.typography.bodySmall
)
}
}
}
}

FloatingActionButton(onClick = { /* yeni not dialogu */ }) {
Icon(Icons.Default.Add, contentDescription = "Ekle")
}
}
}

6. Android Entegrasyonu​

androidApp/src/main/kotlin/MainActivity.kt

Kod:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

setContent {
MaterialTheme {
NotesScreen()
}
}
}
}

7. iOS Entegrasyonu (SwiftUI Wrapper)​

iosApp/ContentView.swift

Kod:
import SwiftUI
import shared  // shared framework

struct ContentView: View {
let viewModel = NotesViewModel(/* DI */)

var body: some View {
NotesScreenKt.NotesScreen(
viewModel: viewModel,
onNoteClick: { note in
// Detay ekranına git
}
)
}
}

8. İleri Seviye İpuçları (2026)​

  • Koin veya Kodein ile DI
  • Ktor Client ile backend sync (shared network layer)
  • Compose for Web ile browser desteği
  • Baseline Profiles + Compose Compiler optimizasyonu
  • Xcode’da preview için SwiftUI + UIKit interop
 
Üst