Blog Infos
Author
Published
Topics
, , ,
Author
Published

ai generated image

Did you know that almost all ViewModels are unstable?

When we first learn Compose,
we’re taught to use a stable class rather than an unstable class.

But ViewModels are unstable.
So why is it that no one says anything when we use unstable ViewModels?

How does Compose determine stability?

It is necessary to understand how the Compose compiler determines stability.

A class is considered stable if all its members are immutable or
types that are traceable by the Compose runtime.

data class Stable(
    val a: Int, // Stable
    val b: MutableState<Int>, // Stable, Traceable in the Compose runtime
    val c: SnapshotStateList<Int> // Stable, Traceable in the Compose runtime
)

data class Unstable(
    var b: Int // Unstable
)

data class Runtime(
    val i: Interface // Uncertain, compose compiler don't know which implementation will be used at runtime.
)

interface Interface {
    data object Impl : Interface
}

 

We can expect the compiler to determine stability as follows.

stable class Stable {
  stable val a: Int
  stable val b: MutableState<Int>
  stable val c: SnapshotStateList<Int>
  <runtime stability> = Stable
}
unstable class Unstable {
  stable var b: Int
  <runtime stability> = Unstable
}
runtime class Runtime {
  runtime val i: Interface
  <runtime stability> = Uncertain(Interface)
}

 

However, this determination applies only within the module where the Compose compiler plugin is declared.

When Stability Crosses Module Boundaries

I will demonstrate an example of creating a stable model in the Data Layer and injecting it into the ViewModel.

stableModel in data layer

viewModel in presentation layer

If you’ve injected a generally stable class,
you would naturally expect the ViewModel to be stable as well.

However, the result is as follows.

result

In this way, we can see that classes receiving injections from other layers also become unstable.

Why Unstable ViewModels Are Okay in Compose

A ViewModel typically injects a Repository or UseCase and acts as the connection point between the UI and business logic.

class ViewModel(
    private val repository: Repository, // from domain or data layer
    private val useCase: UseCase // from domain layer
) : ViewModel()

 

As we saw earlier, classes that inject classes from other layers are unstable.

Therefore, the ViewModel should also be an unstable class.

Yet we still wonder:
If the ViewModel is unstable, why is it acceptable?

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

,

Reimagining Android Dialogs with Jetpack Compose

Traditional Android dialogs are hard to test, easy to leak, and painful to customize — and in a world of Compose-first apps, they’re overdue for an upgrade.
Watch Video

Reimagining Android Dialogs with Jetpack Compose

Keith Abdulla
Staff Android Engineer, Design Systems
Block

Reimagining Android Dialogs with Jetpack Compose

Keith Abdulla
Staff Android Engine ...
Block

Reimagining Android Dialogs with Jetpack Compose

Keith Abdulla
Staff Android Engineer, D ...
Block

Jobs

Answer

The reason is simple.
We don’t pass the ViewModel itself to the composable we pass the stable state within the ViewModel.

data class TestState(
    val data: String
)

@Composable
fun Screen(
    viewModel: TestViewModel
) {
    val state by viewModel.state.collectAsState()

    Child(state)
}

@Composable
fun Child(
    state: TestState
) {
    Text(state.data)
}

 

A ViewModel is typically created only once in the top-level composable and passed as an argument. After that, it collects the state it manages and passes it down to child composables. Therefore, even if the ViewModel is unstable, it doesn’t actually cause problems.

In Closing

For monolithic projects, this isn’t a major issue.
Since all code compiles within a single module, stability assessments are straightforward.

However, if you’re considering a multi-module architecture and interested in Compose’s stability concepts,
this article will definitely be helpful.

This article was previously published on proandroiddev.com

Menu