ViewModel را برای KMP تنظیم کنید

AndroidX ViewModel به عنوان یک پل عمل می کند و یک قرارداد واضح بین منطق تجاری مشترک و اجزای رابط کاربری شما ایجاد می کند. این الگو به اطمینان از سازگاری داده‌ها در بین پلتفرم‌ها کمک می‌کند، در حالی که رابط‌های کاربری را قادر می‌سازد برای ظاهر متمایز هر پلتفرم سفارشی شوند. می‌توانید با Jetpack Compose در Android و SwiftUI در iOS به توسعه UI خود ادامه دهید.

درباره مزایای استفاده از ViewModel و همه ویژگی‌های مستندات اولیه ViewModel بیشتر بخوانید.

وابستگی ها را تنظیم کنید

برای راه اندازی KMP ViewModel در پروژه خود، وابستگی را در فایل libs.versions.toml تعریف کنید:

[versions]
androidx-viewmodel = 2.9.3

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

و سپس آرتیفکت را به فایل build.gradle.kts برای ماژول KMP خود اضافه کنید و وابستگی را به عنوان api اعلام کنید، زیرا این وابستگی به چارچوب باینری صادر می شود:

// 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)
}

API های ViewModel را برای دسترسی از سویفت صادر کنید

به‌طور پیش‌فرض، هر کتابخانه‌ای که به پایگاه کد خود اضافه می‌کنید، به‌طور خودکار به چارچوب باینری صادر نمی‌شود. اگر API ها صادر نمی شوند، فقط در صورتی از چارچوب باینری در دسترس هستند که از آنها در کد مشترک استفاده کنید (از مجموعه منبع iosMain یا commonMain ). در آن صورت، APIها حاوی پیشوند بسته خواهند بود، برای مثال یک کلاس ViewModel به عنوان کلاس Lifecycle_viewmodelViewModel در دسترس خواهد بود. برای اطلاعات بیشتر در مورد صادرات وابستگی ها ، وابستگی های صادراتی به باینری ها را بررسی کنید.

برای بهبود تجربه، می‌توانید وابستگی ViewModel را به چارچوب باینری با استفاده از تنظیمات export در فایل build.gradle.kts صادر کنید، جایی که چارچوب باینری iOS را تعریف می‌کنید، که باعث می‌شود APIهای ViewModel مستقیماً از کد سوئیفت مانند کد Kotlin قابل دسترسی باشند:

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"
  }
}

(اختیاری) استفاده از viewModelScope در دسکتاپ JVM

هنگام اجرای برنامه های مشترک در ViewModel، ویژگی viewModelScope به Dispatchers.Main.immediate گره خورده است که ممکن است به طور پیش فرض در دسکتاپ در دسترس نباشد. برای اینکه به درستی کار کند، وابستگی kotlinx-coroutines-swing را به پروژه خود اضافه کنید:

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

برای جزئیات بیشتر به اسناد Dispatchers.Main مراجعه کنید.

از ViewModel از commonMain یا androidMain استفاده کنید

هیچ الزام خاصی برای استفاده از کلاس ViewModel در commonMain مشترک و همچنین از androidMain sourceSet وجود ندارد. تنها نکته این است که شما نمی توانید از هیچ API مخصوص پلتفرم استفاده کنید و باید آنها را انتزاع کنید. به عنوان مثال، اگر از یک Application Android به عنوان پارامتر سازنده ViewModel استفاده می کنید، باید با انتزاع کردن آن از API خارج شوید.

اطلاعات بیشتر درباره نحوه استفاده از کد پلتفرم خاص در کد پلتفرم خاص در Kotlin Multiplatform موجود است.

به عنوان مثال، در قطعه زیر یک کلاس ViewModel با کارخانه آن است که در commonMain تعریف شده است:

// 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()

سپس، در کد UI خود، می توانید ViewModel را طبق معمول بازیابی کنید:

// androidApp/ui/MainScreen.kt

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

از ViewModel از SwiftUI استفاده کنید

در Android، چرخه حیات ViewModel به طور خودکار مدیریت می شود و به یک ComponentActivity ، Fragment ، NavBackStackEntry (Navigation 2)، یا rememberViewModelStoreNavEntryDecorator (Navigation 3) تقسیم می شود. با این حال، SwiftUI در iOS هیچ معادل داخلی برای AndroidX ViewModel ندارد.

برای اشتراک گذاری ViewModel با برنامه SwiftUI خود، باید مقداری کد راه اندازی اضافه کنید.

یک تابع برای کمک به ژنریک ایجاد کنید

نمونه سازی یک نمونه کلی ViewModel از ویژگی بازتاب مرجع کلاس در Android استفاده می کند. از آنجایی که ژنریک های Objective-C از همه ویژگی های کاتلین یا سوئیفت پشتیبانی نمی کنند ، نمی توانید یک ViewModel از نوع عمومی را مستقیماً از سویفت بازیابی کنید.

برای حل این مشکل، می‌توانید یک تابع کمکی ایجاد کنید که از ObjCClass به جای نوع ژنریک استفاده می‌کند و سپس از getOriginalKotlinClass برای بازیابی کلاس ViewModel برای نمونه‌سازی استفاده می‌کند:

// 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]
}

سپس، وقتی می‌خواهید تابع را از سویفت فراخوانی کنید، می‌توانید یک تابع عمومی از نوع T : ViewModel و از T.self استفاده کنید، که می‌تواند ObjCClass به تابع resolveViewModel منتقل کند.

دامنه ViewModel را به چرخه حیات SwiftUI متصل کنید

مرحله بعدی ایجاد یک IosViewModelStoreOwner است که رابط های ObservableObject و ViewModelStoreOwner (پروتکل ها) را پیاده سازی می کند. دلیل ObservableObject این است که بتوان از این کلاس به عنوان یک @StateObject در کد SwiftUI استفاده کرد:

// 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()
    }
}

این مالک امکان بازیابی چندین نوع ViewModel را می‌دهد، مانند Android. چرخه عمر آن ViewModel ها زمانی که صفحه ای که از IosViewModelStoreOwner استفاده می کند پاک می شود و deinit فراخوانی می کند. می‌توانید در اسناد رسمی درباره بی‌اصل‌سازی اطلاعات بیشتری کسب کنید.

در این مرحله، می‌توانید IosViewModelStoreOwner به‌عنوان یک @StateObject در یک نمای SwiftUI نمونه‌سازی کنید و تابع viewModel را برای بازیابی ViewModel فراخوانی کنید:

// 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 موجود نیست

برخی از APIهای موجود در اندروید در کاتلین چندپلتفرم در دسترس نیستند.

ادغام با Hilt

از آنجایی که Hilt برای پروژه‌های چند پلتفرمی Kotlin در دسترس نیست، نمی‌توانید مستقیماً از ViewModels با حاشیه‌نویسی @HiltViewModel در sourceSet commonMain استفاده کنید. در این صورت باید از چارچوب DI جایگزین استفاده کنید، به عنوان مثال، Koin ، kotlin-inject ، Metro ، یا Kodein . می‌توانید تمام چارچوب‌های DI را که با Kotlin Multiplatform کار می‌کنند در klibs.io پیدا کنید.

جریان ها را در SwiftUI مشاهده کنید

مشاهده Coroutines Flow در SwiftUI مستقیماً پشتیبانی نمی شود. با این حال، می‌توانید از KMP-NativeCoroutines یا کتابخانه SKIE برای اجازه دادن به این ویژگی استفاده کنید.