Sharing View Models in Kotlin Multiplatform Mobile (iOS and Android)
Tags: kotlin, kmm, ios, android
Published: 2023-08-11 14:54:39

Kotlin Multiplatform Mobile (KMM) stands out as a unique cross-platform technology, distinguishing itself from other solutions available in the market. Unlike many other frameworks, KMM enables the sharing of only the business logic between platforms, while the user interface remains fully native—a remarkable advantage. This approach typically involves forming three distinct teams to develop your application: iOS, Android, and shared developers. The iOS team concentrates on the native UI for the iOS platform, the Android team takes care of the Android UI, and the shared developers focus on crafting the business logic. This collaborative model facilitates efficient development, particularly when you strive to maximize code sharing. KMM simplifies sharing repositories, services, and databases, provided that your technology stack aligns with its principles.

One of the intriguing challenges KMM presents is the sharing of view models between iOS and Android, a task that brings increased complexity but offers substantial rewards.

## The Case for Sharing View Models

I embarked on my journey with Kotlin Multiplatform Mobile while working on my side project, [Wolfie.app](https://wolfie.app), an innovative pet companion app designed for both iOS and Android platforms. When considering the best technology for my app, I weighed the merits of Flutter, React Native, and Kotlin Multiplatform Mobile. Opting for Flutter or React Native would have entailed compromising the native user experience—an outcome I was keen to avoid. My vision was to provide iOS and Android users with a seamlessly native experience. Additionally, taking the route of full native development would likely have consumed all my available time, given that I was a one-person team handling web, backend, and mobile development. This context made Kotlin Multiplatform Mobile the ideal choice.

As an iOS developer primarily, I found the initial steps in adopting KMM a bit steeper than they might have been for an Android developer. For those more inclined towards Android development, the checklist would be relatively shorter. Here are some key components to consider:

* **Kotlin**: A fundamental requirement. Even if you have a background in iOS development, Kotlin is approachable and can be easily learned, even on your inaugural project.
* **Swift**: An obvious necessity for iOS development. If you lack a Mac, acquiring one becomes imperative.
* **SwiftUI and Jetpack Compose**: Declarative development paradigms that bring a new level of sophistication to UI design.
* **Coroutines**: This concurrency framework plays a pivotal role, as most of your logic, repositories, and database operations will rely on it. If you possess experience with reactive programming, this will serve as a valuable foundation. Notably, coroutines also find application in iOS development.
* [**KTOR**](http://ktor.io): Your steadfast ally for any API connections.

Beyond these essentials, a plethora of libraries awaits your consideration. However, this article will specifically delve into the topic of sharing view models, exploring two noteworthy libraries and the do-it-yourself approach.

## \## moko-MVVM [github](https://github.com/icerockdev/moko-mvvm)

tl;dr
Licence: Apache 2.0 
First public release was on Oct, 2019.  
760 stars on GitHub 
34 open issues 
14 contributors

When it comes to sharing view models in Kotlin Multiplatform Mobile (KMM), the **moko-MVVM** library ([GitHub](https://github.com/icerockdev/moko-mvvm)) has garnered significant attention. Boasting an Apache 2.0 license, this library made its initial public release in October 2019. With an impressive count of 760 stars on GitHub, moko-MVVM showcases its popularity within the developer community. Backed by a team that has honed their focus on KMM for years, this library presents itself as a potent contender. However, while it holds great promise, there are some considerations to weigh before adoption.

A key advantage of moko-MVVM is its robust backing by a company committed to advancing the KMM ecosystem. Yet, an inherent trade-off exists, centered around the library's alignment with declarative UI implementations. It's important to note that moko-MVVM leans more towards UIKit rather than SwiftUI, potentially impacting its compatibility with the latest technologies. As of my writing, there appears to be no library compatible with Xcode 14.3.1, or at least the compatibility cannot be achieved seamlessly following the documentation. Given these circumstances, I've opted to defer my utilization of moko-MVVM for the time being.

Notably, moko-MVVM finds synergy with another noteworthy library from the same developer group: **kswift** ([GitHub](https://github.com/icerockdev/moko-kswift)). This complementary library offers a wealth of Kotlin/Native API translations to Swift, enhancing cross-platform development. Additionally, it facilitates coroutine support on iOS, further streamlining your development workflow.

The promise held by moko-MVVM is undeniable, and its alignment with a company devoted to KMM adds to its credibility. However, for those heavily invested in SwiftUI and seeking seamless compatibility with the latest iOS technologies, careful consideration of its UIKit-centric approach is warranted. As the KMM landscape continues to evolve, the synergy between libraries like moko-MVVM and kswift could become increasingly compelling.

## KMM-ViewModel [github](https://github.com/rickclephas/KMM-ViewModel)

tl;dr
Licence: MIT
First public release was on Dec, 2022. 
353 stars on GitHub
4 open issues
2 contributors

In the realm of sharing view models across platforms with Kotlin Multiplatform Mobile (KMM), the **KMM-ViewModel** library ([GitHub](https://github.com/rickclephas/KMM-ViewModel)) emerges as a notable contender. With a MIT license and its initial public release in December 2022, KMM-ViewModel is a relatively new player in the field, yet it has already garnered attention with 353 stars on GitHub. While it's in the alpha phase, its compatibility with SwiftUI right out of the box sets it apart. Moreover, its MIT licensing lends an advantageous flexibility in terms of potential maintenance efforts, particularly in the event of shifts in support.

Despite its current developmental stage, KMM-ViewModel offers a remarkable feature: seamless integration with SwiftUI, a key framework in modern iOS app development. This attribute could be particularly enticing for those who prioritize SwiftUI's declarative and intuitive approach to user interface design. By providing a direct bridge between the KMM-ViewModel library and SwiftUI, developers can potentially achieve a harmonious synergy between platforms, promoting a consistent and native-like experience for users across iOS and Android.

It's worth noting that KMM-ViewModel is an evolving library, still traversing its alpha phase. However, its alignment with SwiftUI underscores its potential to be a powerful tool for cross-platform development. Given its relatively recent entry into the landscape, it's prudent to monitor its progression as it matures further.

An interesting facet of KMM-ViewModel lies in its reliance on the **KMP-NativeCoroutines** library ([GitHub](https://github.com/rickclephas/KMP-NativeCoroutines)). This auxiliary library complements KMM-ViewModel by providing essential coroutine support on iOS—a critical component of modern mobile app development. Coroutines are pivotal for asynchronous programming and managing concurrency, and the inclusion of KMP-NativeCoroutines underscores the commitment to a robust development experience across platforms.

While KMM-ViewModel remains in alpha, its compatibility with SwiftUI and emphasis on coroutine support signal a potential breakthrough for seamless cross-platform view model sharing. As it continues to mature and gather feedback from the developer community, it could become a vital asset in the KMM toolkit.

## Custom Solutions: Crafting Coroutines Support

In the pursuit of harmonizing coroutine support for both iOS and Android platforms within your Kotlin Multiplatform Mobile (KMM) project, custom solutions offer a versatile approach. By crafting shared functions and classes, you can bridge the gap and ensure seamless integration of asynchronous operations.

Let's break down the custom solution step by step:

### 1. `StateFlowClass`: Shared State Flow

```kotlin
// shared/src/commonMain/kotlin/me/blanik/sample/Couritines.kt

package me.blanik.sample

import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

class StateFlowClass<T>(private val delegate: StateFlow<T>) : StateFlow<T> by delegate {
    fun subscribe(block: (T) -> Unit) = GlobalScope.launch(Dispatchers.IO) {
        delegate.collect {
            block(it)
        }
    }
}

fun <T> StateFlow<T>.asStateFlowClass(): StateFlowClass<T> = StateFlowClass(this)
```

This snippet establishes a `StateFlowClass` that wraps a `StateFlow` and offers a `subscribe` function. It employs coroutines to bridge the gap between platforms, ensuring that data collection happens on the appropriate thread.

### 2. Shared Dispatchers

```kotlin
// shared/src/commonMain/kotlin/me/blanik/sample/Dispatchers.kt
package me.blanik.sample.database

import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers

expect val Dispatchers.IO: CoroutineDispatcher
```

In this section, shared dispatchers are set up for both platforms. On Android, we have a straightforward mapping to Android's `Dispatchers.IO`. For iOS, we'll require more context:

### 3. Custom Coroutine Dispatcher for iOS

```kotlin
// shared/src/iosMain/kotlin/me/blanik/sample/Disaptchers.kt
package me.blanik.sample

import kotlinx.coroutines.*
import platform.darwin.*

actual val Dispatchers.IO: CoroutineDispatcher
    get() = IODispatcher

@OptIn(InternalCoroutinesApi::class)
private object IODispatcher : CoroutineDispatcher(), Delay {
    // Implementation details...
}
```

In this code block, we define a custom coroutine dispatcher for iOS. This dispatcher takes advantage of iOS's dispatch mechanisms to ensure that coroutines run on the main queue and handle delays appropriately.

### 4. Flow Utilities

```kotlin
// shared/src/commonMain/kotlin/me/blanik/sample/FlowUtils.kt
package me.blanik.sample.database

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach

class CFlow<T>(private val origin: Flow<T>) : Flow<T> by origin {
    // Implementation details...
}

// Helper extension
internal fun <T> Flow<T>.wrap(): CFlow<T> = CFlow(this)

// Remove when Kotlin's Closeable is supported in K/N
interface Closeable {
    fun close()
}
```

This snippet introduces `CFlow`, a class that enables the consumption of Flow-based APIs from Swift/Objective-C. It offers a clean way to handle subscriptions from these languages.

### 5. Platform-Specific Implementations

#### Android:

```kotlin
// shared/src/androidMain/kotlin/me/blanik/sample/Dispatchers.kt
package me.blanik.sample

import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers

actual val Dispatchers.IO: CoroutineDispatcher
    get() = Dispatchers.IO
```

#### iOS:

```kotlin
// shared/src/iosMain/kotlin/me/blanik/sample/Disaptchers.kt
package me.blanik.sample

import kotlinx.coroutines.*
import platform.darwin.*

actual val Dispatchers.IO: CoroutineDispatcher
    get() = IODispatcher

@OptIn(InternalCoroutinesApi::class)
private object IODispatcher : CoroutineDispatcher(), Delay {
    // Implementation details...
}
```

Here, we finalize the setup by providing platform-specific implementations for dispatchers. For Android, we simply map to Android's built-in dispatcher. For iOS, we delve into the custom dispatcher that bridges the gap between Kotlin coroutines and iOS's dispatch mechanism.

With these components in place, you've created a comprehensive solution for managing coroutines and asynchronous operations within your KMM project. This approach empowers your project to handle concurrency seamlessly on both iOS and Android platforms, promoting consistent functionality and performance.

## Implementing KMM-ViewModel: A Step-by-Step Guide

Incorporating KMM-ViewModel into your Kotlin Multiplatform Mobile (KMM) project opens up new avenues for seamless view model sharing across platforms. Let's dive into the implementation process step by step:

1. **Upgrade Your Project to Kotlin 1.9:**

   In your `build.gradle.kts` file, update your Kotlin version to 1.9.0:

   ```kotlin
   plugins {
       id("com.android.application").version("8.1.0").apply(false)
       id("com.android.library").version("8.1.0").apply(false)
       kotlin("android").version("1.9.0").apply(false)
       kotlin("multiplatform").version("1.9.0").apply(false)
   }
   ```

2. **Add Required Libraries to Your Shared Module:**

   Update your `shared/build.gradle.kts` file to include the necessary dependencies:

   ```kotlin
   sourceSets {
       val multiplatformSettingsVersion = "1.0.0"
       val kmmViewModelVersion = "1.0.0-ALPHA-12"
   
       all {
           languageSettings.optIn("kotlin.experimental.ExperimentalObjCName")
       }
   
       val commonMain by getting {
           dependencies {
               implementation("com.russhwolf:multiplatform-settings-no-arg:$multiplatformSettingsVersion")
               implementation("com.russhwolf:multiplatform-settings-serialization:$multiplatformSettingsVersion")
               implementation("com.russhwolf:multiplatform-settings-coroutines:$multiplatformSettingsVersion")
               implementation("com.rickclephas.kmm:kmm-viewmodel-core:$kmmViewModelVersion")
           }
       }
   }
   ```

3. **Sync Your Gradle Files:**

   Make sure to sync your Gradle files after adding the new dependencies.

4. **Update `Podfile` for iOS:**

   In your `iosApp` directory, update the `Podfile` to include the required pods:

   ```ruby
   target 'iosApp' do
     use_frameworks!
     platform :ios, '14.1'
     pod 'shared', :path => '../shared'
     pod 'KMPNativeCoroutinesAsync', '1.0.0-ALPHA-13'
     pod 'KMPNativeCoroutinesCombine', '1.0.0-ALPHA-13'
     pod 'KMPNativeCoroutinesRxSwift', '1.0.0-ALPHA-13'
     pod 'KMMViewModelSwiftUI', '1.0.0-ALPHA-12'
   end
   ```

   Run `pod install` in your `iosApp` directory.

5. **Create Your First Shared View Model:**

   Create a new Kotlin file, `SignInViewModel.kt`, in your shared module:

   ```kotlin
   package me.blanik.sample
   
   import com.rickclephas.kmm.viewmodel.*
   import com.rickclephas.kmp.nativecoroutines.NativeCoroutinesState
   import kotlinx.coroutines.flow.*
   
   open class SignInViewModel: KMMViewModel() {
       private val _email = MutableStateFlow(viewModelScope, "")
       private val _password = MutableStateFlow(viewModelScope, "")
   
       @NativeCoroutinesState
       val email = _email.asStateFlow()
       @NativeCoroutinesState
       val password = _password.asStateFlow()
   
       fun setEmail(email: String) {
           _email.value = email
       }
   
       fun setPassword(password: String) {
           _password.value = password
       }
   }
   ```

6. **Add iOS Implementation:**

   In your `iosApp` directory, create a Swift file named `KMMViewModel.swift`:

   ```swift
   import KMMViewModelCore
   import shared
   
   extension Kmm_viewmodel_coreKMMViewModel: KMMViewModel { }
   ```

   Also you need to update your `ContentView.swift` file:

   ```swift
   import SwiftUI
   import KMMViewModelSwiftUI
   import shared
   
   extension ContentView {
       class ViewModel: shared.SignInViewModel {}
   }
   
   struct ContentView: View {
       @StateViewModel var viewModel = ViewModel()
   
       var body: some View {
           VStack {
               List {
                   Section(header: Text("Input")) {
                       HStack {
                           Text("Email")
   
                           TextField("Email here", text: Binding(get: {
                               viewModel.email
                           }, set: { value in
                               viewModel.setEmail(email: value)
                           }))
                       }
   
                       HStack {
                           Text("Password")
   
                           SecureField("Type here", text: Binding(get: {
                               viewModel.password
                           }, set: { value in
                               viewModel.setPassword(password: value)
                           }))
                       }
                   }
                   
                   Section(header: Text("Output")) {
                       HStack {
                           Text("Email")
   
                           Text(viewModel.email)
                       }
                       HStack {
                           Text("Password")
   
                           Text(viewModel.password)
                       }
                   }
               }
           }
       }
   }
   
   struct ContentView_Previews: PreviewProvider {
   	static var previews: some View {
   		ContentView()
   	}
   }
   ```

   Binding kotlin couritines to SwiftUI `TextField` components:

   Couritines is one way communication. You need to attach them as the `@State` (or `@Published`) values which two-way communication and natively supported by iOS. To archive that you can use create custom binding for getter and setter.

   ```swift
   Binding(get: {
       …
   }, set: { value in
       viewModel.…(…)
   })
   ```

7. **Create Sign In Screen:**

   In your Android module, create a Composable function for the sign-in screen:

   ```kotlin
   @Composable
   fun SignInScreen(
       signInViewModel: SignInViewModel = SignInViewModel()
   ) {
       val emailState by signInViewModel.email.collectAsState()
       val passwordState by signInViewModel.password.collectAsState()
   
       Column {
           Text("Input")
           OutlinedTextField(
               label = { Text(text = "Email") },
               value = emailState,
               onValueChange = signInViewModel::setEmail
           )
           OutlinedTextField(
               label = { Text(text = "Password") },
               value = passwordState,
               onValueChange = signInViewModel::setPassword,
               visualTransformation = PasswordVisualTransformation(),
               keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password)
           )
           Text("Output")
           Text(text = emailState)
           Text(text = passwordState)
       }
   }
   ```

8. **Implement Sign In Screen in MainActivity:**

   In your Android `MainActivity`, use the `SignInScreen` Composable function:

   ```kotlin
   class MainActivity : ComponentActivity() {
       private val viewModel: SignInViewModel by viewModels()
   
       override fun onCreate(savedInstanceState: Bundle?) {
           super.onCreate(savedInstanceState)
   
           setContent {
               MyApplicationTheme {
                   Surface(
                       modifier = Modifier.fillMaxSize(),
                       color = MaterialTheme.colors.background
                   ) {
                       Column {
                           SignInScreen(viewModel)
                       }
                   }
               }
           }
       }
   }
   ```

With these steps, you've successfully integrated KMM-ViewModel into your project, enabling seamless view model sharing between iOS and Android platforms. This comprehensive guide should help you navigate through the implementation process and leverage the power of shared view models in your Kotlin Multiplatform Mobile development journey.

## Extra – API Connection in KMM

Adding API connectivity to your Kotlin Multiplatform Mobile (KMM) project through Ktor can greatly enhance your app's functionality. Let's go through the steps of integrating API calls into your existing KMM project:

1. **Update Shared Build Gradle Configuration:**

   In your `shared/build.gradle.kts` file, add Ktor dependencies to your common source set:

   ```kotlin
   // shared/build.gradle.kts
   kotlin {
       // ...
       
       sourceSets {
           val ktorVersion = "2.3.3"
   
           val commonMain by getting {
               dependencies {
                   implementation("io.ktor:ktor-client-core:$ktorVersion")
                   implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion")
                   implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion")
                   implementation("io.ktor:ktor-client-serialization:$ktorVersion")
                   implementation("io.ktor:ktor-client-logging:$ktorVersion")
                   implementation("io.ktor:ktor-client-auth:$ktorVersion")
               }
           }
   
           // ...
       }
   }
   ```

2. **Create API Models:**

   Define your API response models and payloads. For example, create `ApiAuthSignInResponse` and `ApiAuthSignInPayload` classes in your shared module:

   ```kotlin
   // shared/src/commonMain/kotlin/me/blanik/sample/network/ApiAuthSignIn.kt
   package me.blanik.sample.network
   
   import kotlinx.serialization.Serializable
   
   @Serializable
   data class ApiAuthSignInResponse(
       val accessToken: String,
       val refreshToken: String? = null
   )
   
   @Serializable
   data class ApiAuthSignInPayload(
       val username: String,
       val password: String,
       val keepSignIn: Boolean? = null,
       val device: String? = null
   )
   ```

3. **Create API Response Handling:**

   Define a class to represent API error responses and responses containing data. For example:

   ```kotlin
   // shared/src/commonMain/kotlin/me/blanik/sample/network/ApiResponse.kt
   package me.blanik.sample.network
   
   import io.ktor.util.date.GMTDate
   import kotlinx.serialization.Serializable
   
   @Serializable
   data class ApiError(
       val statusCode: Int = 400,
       val message: String = "",
       val timestamp: String = GMTDate().toString(),
       val errors: List<ApiErrorErrors>? = null
   )
   
   @Serializable
   data class ApiErrorErrors(
       val property: String = "",
       val children: List<ApiErrorErrors> = emptyList(),
       val constraints: Map<String, String> = emptyMap()
   )
   
   class ApiResponse<T>(success: T?, error: ApiError?) {
       var success: T? = success
       var failure: ApiError? = error
   }
   ```

4. **Create API Service:**

   Implement your API service using Ktor's HTTP client. Define a class that encapsulates API endpoints and handles API requests. For example:

   ```kotlin
   // shared/src/commonMain/kotlin/me/blanik/sample/network/WolfieApi.kt
   package me.blanik.sample.network
   
   import io.ktor.client.*
   import io.ktor.client.call.receive
   import io.ktor.client.request.*
   import io.ktor.http.ContentType
   import io.ktor.http.HttpHeaders
   import io.ktor.http.contentType
   import io.ktor.http.isSuccess
   import io.ktor.serialization.Serializable
   import kotlinx.serialization.json.Json
   
   class WolfieApi {
       private val client by lazy {
           HttpClient {
               install(JsonFeature) {
                   serializer = KotlinxSerializer(Json {
                       ignoreUnknownKeys = true
                       prettyPrint = true
                   })
               }
           }
       }
   
       suspend fun authSignIn(payload: ApiAuthSignInPayload): ApiResponse<ApiAuthSignInResponse> {
           val response = client.post<HttpResponse>("$API_BASE_URL$AUTH_SIGN_IN") {
               contentType(ContentType.Application.Json)
               body = payload
           }
   
           return if (response.status.isSuccess()) {
               ApiResponse(response.receive(), null)
           } else {
               ApiResponse(null, response.receive())
           }
       }
   
       companion object {
           private const val API_BASE_URL = "https://api-x.wolfie.app/v2"
           private const val AUTH_SIGN_IN = "/auth/sign-in"
       }
   }
   ```

   This class defines a function `authSignIn` that sends a POST request to the specified API endpoint, using the provided payload. It returns an `ApiResponse` containing either a successful response or an error response.

5. **Use API in Shared View Model:**

   Integrate the API service into your shared view model. Use the `WolfieApi` class to make API calls, handle responses, and update the view model's state accordingly. For instance:

   ```kotlin
   // shared/src/commonMain/kotlin/me/blanik/sample/SignInViewModel.kt
   import me.blanik.sample.network.ApiAuthSignInPayload
   import me.blanik.sample.network.WolfieApi
   
   enum class ApiState {
       INIT,
       PENDING,
       SUCCESS,
       FAILURE
   }
   
   open class SignInViewModel : KMMViewModel() {
       private val wolfieApi = WolfieApi()
   
       // ...
   
       @NativeCoroutinesState
       val state = _state.asStateFlow()
       
       @NativeCoroutinesState
       val errorMessage = _errorMessage.asStateFlow()
   
       // ...
   
       suspend fun signIn() {
           _state.value = ApiState.PENDING
   
           val response = wolfieApi.authSignIn(
               ApiAuthSignInPayload(
                   username = _email.value,
                   password = _password.value
               )
           )
   
           if (response.success != null) {
               _state.value = ApiState.SUCCESS
           } else {
               _state.value = ApiState.FAILURE
               _errorMessage.value = response.failure?.message ?: null
           }
       }
   }
   ```

6. **Update iOS Implementation:**

   In your iOS implementation (Swift), you can display the API state and error message:

   ```swift
   // iosApp/iosApp/ContentView.swift
   // ...
   struct ContentView: View {
       // ...
   
       var body: some View {
           VStack {
               // ...
               Section(header: Text("Output")) {
                   // ...
                   HStack {
                       Text("State")
                       Text(viewModel.state.rawValue)
                   }
                   HStack {
                       Text("Error message")
                       Text(viewModel.errorMessage ?? "—")
                   }
               }
               // ...
               Section(header: Text("Action")) {
                   Button("Sign in") {
                       Task {
                           try await viewModel.signIn()
                       }
                   }
               }
           }
       }
   }
   ```

7. **Update Android Implementation:**

   In your Android implementation, you can also display the API state and error message:

   ```kotlin
   // androidApp/src/main/java/me/blanik/sample/android/MainActivity.kt
   // ...
   @Composable
   fun SignInScreen(
       signInViewModel: SignInViewModel = SignInViewModel()
   ) {
       // ...
       val stateState by signInViewModel.state.collectAsState()
       val errorMessage by signInViewModel.errorMessage.collectAsState()
   
       Column {
           // ...
           Text("Output")
           Text(text = stateState.name)
           Text(text = errorMessage ?: "—")
           // ...
           Button(onClick = {
               GlobalScope.async(Dispatchers.Main) {
                   signInViewModel.signIn()
               }
           }) {
               Text(text = "Sign in")
           }
       }
   }
   ```

With these steps, you've successfully integrated API connectivity into your KMM project using Ktor. You can now make API calls from shared view models and handle responses across both iOS and Android platforms.

## Summary

Kotlin Multiplatform Mobile (KMM) is rapidly advancing and has become a viable option for production applications. The community is growing, and various libraries are emerging to address different use cases, each with its own set of advantages and considerations. When adopting KMM, it's important to carefully evaluate your project's needs and choose the right solutions.

In this guide, we've explored how to share view models between iOS and Android applications using the KMM-ViewModel library. By creating shared view models and using the KMM-ViewModel library, you can ensure that your business logic is consistent across platforms while allowing native developers to focus on UI development. Keep in mind that shared module developers need to be well-versed in both platforms to effectively support and maintain the shared codebase.

You can find the code samples and implementation details covered in this guide on [GitHub](https://github.com/amadeuszblanik/kmm-samples). If you have any further questions or need assistance, feel free to comment or contact me via [LinkedIn](http://linkedin.com/in/amadeuszblanik/).

KMM offers a promising approach to building cross-platform applications, and as the ecosystem continues to evolve, it's an exciting space to be a part of.