KMP için ViewModel'i ayarlama

AndroidX ViewModel, paylaşılan iş mantığınız ile kullanıcı arayüzü bileşenleriniz arasında net bir sözleşme oluşturarak köprü görevi görür. Bu desen, platformlar arasında veri tutarlılığını sağlamaya yardımcı olurken kullanıcı arayüzlerinin her platformun kendine özgü görünümüne göre özelleştirilmesini sağlar. Android'de Jetpack Compose, iOS'te ise SwiftUI ile kullanıcı arayüzünüzü geliştirmeye devam edebilirsiniz.

ViewModel kullanmanın avantajları ve tüm özellikler hakkında daha fazla bilgiyi ViewModel'in birincil dokümanında bulabilirsiniz.

Bağımlılıkları ayarlama

Projenizde KMP ViewModel'i ayarlamak için bağımlılığı libs.versions.toml dosyasında tanımlayın:

[versions]
androidx-viewmodel = 2.9.3

[libraries]
androidx-lifecycle-viewmodel = { module = "androidx.lifecycle:lifecycle-viewmodel", version.ref = "androidx-viewmodel" }

Ardından, KMP modülünüz için build.gradle.kts dosyasına yapıyı ekleyin ve bağımlılığı api olarak bildirin. Bu bağımlılık, ikili çerçeveye aktarılacaktır:

// You need the "api" dependency declaration here if you want better access to the classes from Swift code.
commonMain.dependencies {
  api(libs.androidx.lifecycle.viewmodel)
}

Swift'ten erişim için ViewModel API'lerini dışa aktarma

Varsayılan olarak, kod tabanınıza eklediğiniz hiçbir kitaplık ikili çerçeveye otomatik olarak aktarılmaz. API'ler dışa aktarılmazsa yalnızca paylaşılan kodda (iosMain veya commonMain kaynak kümesinden) kullanırsanız ikili çerçeveden kullanılabilir. Bu durumda API'ler paket önekini içerir. Örneğin, ViewModel sınıfı Lifecycle_viewmodelViewModel sınıfı olarak kullanılabilir. Bağımlılıkları dışa aktarma hakkında daha fazla bilgi için Bağımlılıkları ikili dosyalara aktarma başlıklı makaleyi inceleyin.

Deneyimi iyileştirmek için ViewModel bağımlılığını, iOS ikili çerçevesini tanımladığınız build.gradle.kts dosyasındaki export kurulumunu kullanarak ikili çerçeveye aktarabilirsiniz. Bu işlem, ViewModel API'lerine Kotlin kodunda olduğu gibi doğrudan Swift kodundan erişilmesini sağlar:

listOf(
  iosX64(),
  iosArm64(),
  iosSimulatorArm64(),
).forEach {
  it.binaries.framework {
    // Add this line to all the targets you want to export this dependency
    export(libs.androidx.lifecycle.viewmodel)
    baseName = "shared"
  }
}

(İsteğe bağlı) JVM masaüstünde viewModelScope kullanma

ViewModel'de eş yordamlar çalıştırılırken viewModelScope özelliği Dispatchers.Main.immediate'ye bağlıdır. Bu özellik, masaüstünde varsayılan olarak kullanılamayabilir. Doğru şekilde çalışması için projenize kotlinx-coroutines-swing bağımlılığını ekleyin:

// Optional if you use JVM Desktop
desktopMain.dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-swing:[KotlinX Coroutines version]")
}

Daha fazla ayrıntı için Dispatchers.Main belgelerini inceleyin.

commonMain veya androidMain ViewModel'ini kullanma

ViewModel sınıfının paylaşılan commonMain veya androidMain sourceSet'te kullanılmasıyla ilgili özel bir şart yoktur. Tek dikkat etmeniz gereken nokta, platforma özgü API'leri kullanamayacağınız ve bunları soyutlamanız gerektiğidir. Örneğin, ViewModel oluşturucu parametresi olarak bir Android Application kullanıyorsanız bu API'yi soyutlayarak bu API'den geçiş yapmanız gerekir.

Platforma özgü kodun nasıl kullanılacağı hakkında daha fazla bilgiyi Kotlin Multiplatform'da platforma özgü kod başlıklı makalede bulabilirsiniz.

Örneğin, aşağıdaki snippet'te commonMain içinde tanımlanmış, fabrikasıyla birlikte bir ViewModel sınıfı yer alıyor:

// commonMain/MainViewModel.kt

class MainViewModel(
    private val repository: DataRepository,
) : ViewModel() { /* some logic */ }

// ViewModelFactory that retrieves the data repository for your app.
val mainViewModelFactory = viewModelFactory {
    initializer {
        MainViewModel(repository = getDataRepository())
    }
}

fun getDataRepository(): DataRepository = DataRepository()

Ardından, kullanıcı arayüzü kodunuzda ViewModel'i her zamanki gibi alabilirsiniz:

// androidApp/ui/MainScreen.kt

@Composable
fun MainScreen(
    viewModel: MainViewModel = viewModel(
        factory = mainViewModelFactory,
    ),
) {
// observe the viewModel state
}

SwiftUI'da ViewModel'i kullanma

Android'de ViewModel yaşam döngüsü otomatik olarak işlenir ve ComponentActivity, Fragment, NavBackStackEntry (Navigation 2) veya rememberViewModelStoreNavEntryDecorator (Navigation 3) ile kapsamlandırılır. Ancak iOS'teki SwiftUI'da AndroidX ViewModel'in yerleşik bir eşdeğeri yoktur.

ViewModel'i SwiftUI uygulamanızla paylaşmak için bazı kurulum kodları eklemeniz gerekir.

Jeneriklerle ilgili yardımcı işlev oluşturma

Genel bir ViewModel örneğini oluşturmak için Android'de sınıf referansı yansıtma özelliği kullanılır. Objective-C jenerikleri, Kotlin veya Swift'in tüm özelliklerini desteklemediğinden jenerik türdeki bir ViewModel'i doğrudan Swift'ten alamazsınız.

Bu soruna yardımcı olması için, genel tür yerine ObjCClass kullanacak bir yardımcı işlev oluşturabilir ve ardından ViewModel sınıfını almak için getOriginalKotlinClass kullanabilirsiniz:

// iosMain/ViewModelResolver.ios.kt

/**
 *   This function allows retrieving any ViewModel from Swift Code with generics. We only get
 *   [ObjCClass] type for the [modelClass], because the interop between Kotlin and Swift code
 *   doesn't preserve the generic class, but we can retrieve the original KClass in Kotlin.
 */
@BetaInteropApi
@Throws(IllegalArgumentException::class)
fun ViewModelStore.resolveViewModel(
    modelClass: ObjCClass,
    factory: ViewModelProvider.Factory,
    key: String?,
    extras: CreationExtras? = null,
): ViewModel {
    @Suppress("UNCHECKED_CAST")
    val vmClass = getOriginalKotlinClass(modelClass) as? KClass<ViewModel>
    require(vmClass != null) { "The modelClass parameter must be a ViewModel type." }

    val provider = ViewModelProvider.Companion.create(this, factory, extras ?: CreationExtras.Empty)
    return key?.let { provider[key, vmClass] } ?: provider[vmClass]
}

Ardından, işlevi Swift'ten çağırmak istediğinizde T : ViewModel türünde genel bir işlev yazabilir ve ObjCClass değerini resolveViewModel işlevine aktarabilen T.self değerini kullanabilirsiniz.

ViewModel kapsamını SwiftUI yaşam döngüsüne bağlama

Bir sonraki adım, ObservableObject ve ViewModelStoreOwner arayüzlerini (protokoller) uygulayan bir IosViewModelStoreOwner oluşturmaktır. ObservableObject, bu sınıfı SwiftUI kodunda @StateObject olarak kullanabilmek için eklenir:

// iosApp/IosViewModelStoreOwner.swift

class IosViewModelStoreOwner: ObservableObject, ViewModelStoreOwner {

    let viewModelStore = ViewModelStore()

    /// This function allows retrieving the androidx ViewModel from the store.
    /// It uses the utilify function to pass the generic type T to shared code
    func viewModel<T: ViewModel>(
        key: String? = nil,
        factory: ViewModelProviderFactory,
        extras: CreationExtras? = nil
    ) -> T {
        do {
            return try viewModelStore.resolveViewModel(
                modelClass: T.self,
                factory: factory,
                key: key,
                extras: extras
            ) as! T
        } catch {
            fatalError("Failed to create ViewModel of type \(T.self)")
        }
    }

    /// This is called when this class is used as a `@StateObject`
    deinit {
        viewModelStore.clear()
    }
}

Bu sahip, Android'de olduğu gibi birden fazla ViewModel türünün alınmasına izin verir. Bu ViewModel'lerin yaşam döngüsü, IosViewModelStoreOwner kullanan ekranın başlatması kaldırıldığında ve deinit çağrıldığında temizlenir. Başlatmayı kaldırma hakkında daha fazla bilgiyi resmi belgelerde bulabilirsiniz.

Bu noktada, IosViewModelStoreOwner öğesini SwiftUI View'da @StateObject olarak başlatabilir ve ViewModel almak için viewModel işlevini çağırabilirsiniz:

// iosApp/ContentView.swift

struct ContentView: View {

    /// Use the store owner as a StateObject to allow retrieving ViewModels and scoping it to this screen.
    @StateObject private var viewModelStoreOwner = IosViewModelStoreOwner()

    var body: some View {
        /// Retrieves the `MainViewModel` instance using the `viewModelStoreOwner`.
        /// The `MainViewModel.Factory` and `creationExtras` are provided to enable dependency injection
        /// and proper initialization of the ViewModel with its required `AppContainer`.
        let mainViewModel: MainViewModel = viewModelStoreOwner.viewModel(
            factory: MainViewModelKt.mainViewModelFactory
        )
        // ...
        // .. the rest of the SwiftUI code
    }
}

Kotlin Multiplatform'da kullanılamaz

Android'de kullanılabilen bazı API'ler Kotlin Multiplatform'da kullanılamaz.

Hilt ile entegrasyon

Hilt, Kotlin Multiplatform projelerinde kullanılamadığından @HiltViewModel ek açıklamalı ViewModel'leri commonMain sourceSet'te doğrudan kullanamazsınız. Bu durumda, Koin, kotlin-inject, Metro veya Kodein gibi alternatif bir DI çerçevesi kullanmanız gerekir. Kotlin Multiplatform ile çalışan tüm DI çerçevelerini klibs.io adresinde bulabilirsiniz.

SwiftUI'da akışları gözlemleme

SwiftUI'da coroutine akışlarını gözlemlemek doğrudan desteklenmez. Ancak bu özelliğe izin vermek için KMP-NativeCoroutines veya SKIE kitaplığını kullanabilirsiniz.