The "Death" of ViewModel? State Management in the KMP Era

Published on Saturday, February 7, 2026

Is the ViewModel dead?

If you asked me this three years ago, I would have said "No, it's the standard." If you ask me today, in the era of Kotlin Multiplatform (KMP) and Compose Multiplatform, the answer is... it’s complicated.


For a decade, androidx.lifecycle.ViewModel has been the security blanket for Android developers. It survived rotation. It survived process death (mostly). It gave us a safe place to park our StateFlows.

But here is the hard truth: The Android ViewModel was never designed for a multiplatform world. It was designed to fix a very specific Android problem (Activity recreation). When you move to KMP, you don't have Activities. You have a shared commonMain codebase that doesn't know (and shouldn't know) what a "configuration change" is.

So, how do we manage state when we can't rely on our favorite Android crutch? Let's explore the three paths facing developers in 2026: The Zombie, The Purist, and The Anarchist.

1. The "Zombie" ViewModel (Google’s KMP Port)

The path of least resistance.

Google saw the writing on the wall. They knew if KMP was going to take off, they couldn't force every Android dev to learn a new architecture overnight. So, starting with Lifecycle 2.8.0, they did something magical: they made androidx.lifecycle multiplatform.

You can now write this in commonMain:

// shared/src/commonMain/kotlin/com/xdroiddev/feature/home/HomeViewModel.kt

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch

class HomeViewModel(
    private val repository: DataRepository
) : ViewModel() {

    private val _state = MutableStateFlow<HomeState>(HomeState.Loading)
    val state = _state.asStateFlow()

    fun loadData() {
        viewModelScope.launch {
            // This scope automatically cancels when the ViewModel clears!
            // Yes, even on iOS!
            _state.value = repository.fetchData()
        }
    }
}

Why it feels like "Cheating"

This looks exactly like the Android code you've written for years. That’s the point.
Under the hood, Google wrote native targets for iOS, Desktop, and Wasm. On Android, it binds to the Activity/Fragment lifecycle. On iOS, it hooks into the deinit of the view controller (or the ComposeUIViewController lifecycle owner).

  • Pros: Zero learning curve. You keep your viewModelScope. Hilt/Koin integrations work almost exactly the same.
  • Cons: It’s still conceptually tied to the "View" lifecycle. It doesn't solve the navigation state problem (handling the back stack state across platforms) on its own; it just solves the data survival problem.

2. The "Purist" Approach (Decompose & MVI)

The path of the architect.

If you talk to hardcore KMP enthusiasts, they will tell you: "ViewModel is an Android artifact. Let it die."

Enter Decompose.

Decompose doesn't try to port Android concepts to iOS. Instead, it introduces a platform-agnostic concept: The Component. A Component is a pure Kotlin class that has its own lifecycle, its own state, and—crucially—is aware of the navigation hierarchy.

In this world, you don't have a ViewModel. You have a HomeComponent.

// shared/src/commonMain/kotlin/com/xdroiddev/feature/home/HomeComponent.kt

class DefaultHomeComponent(
    componentContext: ComponentContext, // The magic "Lifecycle" replacement
    private val repository: DataRepository
) : HomeComponent, ComponentContext by componentContext {

    // "coroutineScope()" is an extension provided by integration libraries
    // effectively replacing viewModelScope
    private val scope = coroutineScope() 

    private val _state = MutableValue(HomeState.Loading)
    override val state: Value<HomeState> = _state

    init {
        scope.launch {
            // Load data
        }
    }
}

The "Mindset Shift"

The biggest difference here is Dependency Injection. In the Android ViewModel world, you ask a ViewModelProvider to give you an instance. In Decompose, you usually do Constructor Injection manually (or assisted) as you traverse the navigation tree.

  • Pros: True platform independence. Robust navigation handling (Back stack management is built-in!). No "magic" Android behaviors.
  • Cons: High boilerplate. You have to learn about ComponentContext, Value<T> (vs StateFlow), and strict state restoration. It scares away junior devs.

3. The "Anarchist" Approach (ScreenModels & Voyager)

The middle ground.

Libraries like Voyager (designed specifically for Compose Multiplatform) try to sit in the middle. They know you like ViewModels, but they also know you need KMP navigation.

They give you a ScreenModel:

class HomeScreenModel(
    private val repository: DataRepository
) : ScreenModel {

    // screenModelScope comes for free
    fun loadData() {
        screenModelScope.launch {
           // ...
        }
    }
}

It looks like a ViewModel, quacks like a ViewModel, but it lives entirely in the Compose ecosystem. If you are going "All In" on Compose Multiplatform (rendering UI with Skia on iOS), this is often the cleanest choice.

The Verdict: Is ViewModel Dead?

No. It was just promoted.

The Android-specific ViewModel is dead. You should strictly avoid importing android.arch.* or relying on Activity context in your view models today.

However, the pattern of the ViewModel—a class that survives configuration changes and manages UI state—is more alive than ever.

My Recommendation for 2026:

  1. If you are migrating an existing Android app to KMP:
    Stick with Google's KMP ViewModel (androidx.lifecycle:lifecycle-viewmodel:2.8.0+). It allows you to move logic to commonMain without rewriting your entire architecture or navigation graph. It is the "safe" bridge.

  2. If you are starting a greenfield KMP project:
    Take a hard look at Decompose. It forces you to write cleaner, more modular code that doesn't care if it's running on Android, iOS, or a terminal window. It’s harder to learn, but it pays off in complex apps.

  3. If you are building a pure Compose Multiplatform app (no SwiftUI):
    Use Voyager or Circuit. They reduce the boilerplate and feel "native" to Compose.

The ViewModel isn't dead; it just lost its dependency on android.os.Bundle. And honestly? Good riddance.