Shared ViewModels & Architecture
The recommended architecture
A clean KMP app layers like this, with the line for “shared” drawn just below the UI:
A shared ViewModel
You can put presentation logic in commonMain and expose the screen as a single StateFlow of a sealed UI state — the same pattern you learned in Kotlin and Compose, now shared.
sealed interface UsersUiState {
object Loading : UsersUiState
data class Success(val users: List<User>) : UsersUiState
data class Error(val message: String) : UsersUiState
}
class UsersViewModel(private val repo: UserRepository) {
private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
private val _state = MutableStateFlow<UsersUiState>(UsersUiState.Loading)
val state: StateFlow<UsersUiState> = _state
fun load() {
scope.launch {
_state.value = UsersUiState.Loading
_state.value = runCatching { repo.getUsers() }
.fold(
onSuccess = { UsersUiState.Success(it) },
onFailure = { UsersUiState.Error(it.message ?: "Error") }
)
}
}
fun clear() { scope.cancel() } // call when the screen goes away
}
How each platform consumes it
- Android — collect
stateas Compose state and render with awhen. - iOS — observe
statefrom Swift (via SKIE or a small wrapper) and drive a SwiftUI view.
The decision “loading vs success vs error” is made once in shared code; both UIs simply display whatever state they’re given.
Tools that make this nicer
- SKIE — native-feeling Flow/suspend in Swift.
- Koin — multiplatform dependency injection to wire repositories into ViewModels.
- SQLDelight — a shared, type-safe database.
- moko-mvvm / KMP-ObservableViewModel — helpers for sharing ViewModels with lifecycle support.
Common mistakes
- Putting platform UI logic into the shared ViewModel.
- Forgetting to cancel the ViewModel’s scope, leaking coroutines.
- Trying to share the UI before the logic layer is solid — share logic first.
Summary: Share ViewModels in common code that expose a single StateFlow of a sealed UI state; let Compose (Android) and SwiftUI (iOS) render it natively. Use Koin, SQLDelight and SKIE to make the shared layer clean and ergonomic.