Two mutables don’t make a right
Tags: kotlin, android, compose
Published: 2021-11-24 00:08:56

{% cta https://blog.zachklipp.com/two-mutables-dont-make-a-right/ %} This post has moved to blog.zachklipp.com. {% endcta %}

---

We’ve all done it: put some data in a list, changed the data around a little, and rendered the list in some UI. Unfortunately, sometimes the UI doesn’t update when you change the data. This post isn’t going to try to cover all the reasons why that might be, but just the most common one, which I’ve seen come up at least a couple times a week.

_This post is largely applicable to any reactive programming library, although it does focus a little extra on Compose in spots. It also focuses on collections, but applies to any data structures you're using to hold state._

## tl;dr

If someone sent you a link to this post as a reply to a question on Slack or somewhere, and you just want a quick answer, here you go:

----

> **Don't put mutable collections inside mutable state holders.**

----

State holders like `MutableStateFlow`, and `MutableState` only send change notifications when the old and new state values are not equal.

- They **will not** send change notifications if you simply change some properties of the object they're holding.
- They **will not** send change notifications if you mutate the object they're holding, then set that same object as the new state. Because the old and new state objects are actually the same object, they will always be considered equal (this is part of the [contract](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-any/equals.html) of the `equals` method in Kotlin and Java).

The correct thing to do is to either:

1. Store an immutable collection in the state holder, and update the collection by creating a new copy. The easiest way to do this is to use the standard Kotlin read-only collections, like `listOf()`, and the standard operators on those collections, like `plus`.
2. Use a special collection type that can send its own change notifications when you mutate it. This only works if you're using Compose's state system, because RxJava, LiveData, Flow, etc. can't provide such collection types. Compose provides two collection types for this: `mutableStateListOf<T>(): SnapshotStateList<T>` and `mutableStateMapOf<K, V>(): SnapshotStateMap<K, V>`. If you really want to use one of these but need a `Set`, you can just use a `SnapshotStateMap` with a value type of `Unit`.

### DON'T DO

```kotlin
var list by mutableStateOf(mutableListOf("a"))
list.add("b")
```

This is just as useless:

```kotlin
var list by mutableStateOf(mutableListOf("a"))
val tempList = list
tempList.add("b")
list = tempList
```

because list and tempList refer to the same object, so `list = tempList` is a no-op. That's the same as doing this:

```kotlin
var list by mutableStateOf(mutableListOf("a"))
list.add("b")
list = list
```

### DO

```kotlin
var list by mutableStateOf(listOf("a"))
list = list + "b"
```

Or

```kotlin
val list = mutableStateListOf("a")
list.add("b")
```

Hopefully that helps, but if you'd like to know _why_ this is the case, please read on.

## Degrees of mutability

When writing stateful code, I find it helpful to think about the number of ways a single value can be changed. I'm not sure if there's a standard term for this concept, so I'll just call it the "degree of mutability". That number should never be more than one. Consider this code:

```kotlin
var data = mutableStateOf(mutableListOf("a"))
```

If you found this in a codebase, and you had to update this value, how would you do it? You've got three options:

1. By re-assigning `data` to point to a different `MutableState`: `data = otherMutableState`
2. By changing the value of the `MutableState`: `data.value = otherList`
3. By changing the list itself: `data.value.add("b")`

This is bad for multiple reasons: it's not clear to readers of the code how they _should_ change the value, and it means it's not safe to pass any of the nested values around because some other code might decide to just re-assign `data`. In this example, the only "correct" place to mutate is the value of the `MutableState`, since that's the only one that will actually cause change notifications to be sent.

Let's dig into the two main concepts at play here: mutable data structures and observing state changes.

## Modifying collections

The Kotlin standard library has two list types: `List<T>` and `MutableList<T>`.

_For brevity I’ll limit the discussion in this section to lists, although the same principles apply to other data structures like maps and sets._

`MutableList`, as the name implies, is a list that has operations to mutate, or change, its contents: add, remove, and replace items. It’s easy to come to the conclusion that the `List` type must therefore be _immutable_. That’s not the case. `List`s are ‘read-only’, but they may or may not be mutable. Given a reference to a `List`, you can’t change its contents yourself, but some other code might be able to. The `MutableList` interface extends the `List` interface, so it’s very easy to create a list that you can change, but pass it around to other code so that code can only read it, even as you’re still making changes. For example:

```kotlin
class Model {
  private val mutableData: MutableList<String> = mutableListOf("a")
  val readOnlyData: List<String> = mutableData
}
```

That said, Kotlin still provides operators like `plus` and `minus` for `Lists` – even though they’re read-only! It can do this because these operators actually copy the list and return a new instance of the list, instead of mutating the list object in-place. This code demonstrates:

```kotlin
/// Read-only Lists

val list = listOf("a")
// Must be a var since we'll assign a new instance.
var longerList = list
longerList = longerList + "b"

// The !== operator compares references.
assert(list !== longerList) 
assert(list != longerList)
assert(longerList == listOf("a", "b"))

/// Mutable Lists

val mutableList = mutableListOf("a")
// Can be a val since we'll modify in-place.
val longerMutableList = mutableList
longerMutableList.add("b")

assert(mutableList === longerMutableList)
// Both lists point to the same object, so they're equal.
assert(mutableList == longerMutableList)
assert(mutableList == mutableListOf("a", "b"))
```

Now that we've reviewed how collection mutation and comparison interact, let's review how common libraries manage state changes.

## Observing state changes

In order for a UI to update when data changes, it needs to be notified when changes happen. Some common tools for doing this on Android are libraries like RxJava and LiveData, as well as Kotlin's Flows and, more recently, Compose’s snapshot state system. All these options actually have very similar APIs for this particular task and they all look something like this:

```kotlin
val someValue: StateHolder<ValueType> = StateHolder(initialValue)
```

The library provides some type that holds a reference to some value that represents a piece of “state” in your program. That holder type lets you do two things: (1) change the value, and (2) subscribe to be notified when the value changes. Two key concepts here are “state” and “change”. If you look at a state value at two different times, and it’s the same value, then from your perspective the state hasn’t changed. Even if, in the meantime, a whole bunch of work has happened behind the scenes to recalculate what the state value is and determined that it should be… the same value. This concept is often referred to in a number of ways, with terms like "idempotency", "conflation", or "de-duping". In all of the aforementioned libraries, setting the state to the current value won't actually send any change notifications.

In practice, this is actually a performance optimization because when the UI is derived from some state, then given two equivalent state values, the same UI should be derived. If the state value hasn't changed, there's no need to update anything in the UI, because nothing would actually change.

Given the pseudo-types from the above code, and the behavior of the libraries mentioned above, this code would send a change notification:

```kotlin
val name = StateHolder("a")
// Change state to a new value
name.value = "b"
```

but this code would not:

```kotlin
val name = StateHolder("a")

// "Change" the state to the same value…
// that is, don't change it at all.
name.value = "a"
```

Which implies that this code would _also_ not send change notifications:

```kotlin
class Model(var name: String)

val model = StateHolder(Model("a"))
val tempModel = model.value

// The name property is a var, so we can just change it.
// However, StateHolder has no way of knowing when
// properties of objects it's holding change.
tempModel.name = "b"

// model.value === tempModel, which implies that
// model.value == tempModel, so this is also a no-op.
model.value = tempModel
```

That code is effectively the same as this, which makes the issue more obvious:

```kotlin
val model = StateHolder(Model("a"))
model.value.name = "b"
model.value = model.value
```

When you change the properties inside the `Model`, you're mutating the same object that the holder already knows about. So when it does its internal comparison of the current value to the new one, it's comparing the same object to itself, and the [contract of Kotlin's `equals` method](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-any/equals.html) requires that an object is always "equal" to itself.

## Collections as state

The above `Model` type was a thinly-veiled placeholder for `MutableList`, `MutableMap`, or any other standard mutable collection type. Substitute `MutableList` for `Model` and you'll get the bad example code at the top of this post. Storing a mutable object inside a state holder, then mutating the object, will not send change notifications unless the mutable object sends its own change notifications.

There are two alternatives, as described near the top of this post, and they each have their own benefits and trade-offs.

### Immutable collections

Immutable collections are, generally, easier to reason about in concurrent code because given an instance of a collection, you can assume that collection will never change until the heat-death of the universe. When using reactive libraries like RxJava or LiveData, using immutable collections is your only real option. And while it might sound expensive, in many cases copying small collections is actually very fast and not worth worrying about.

### Snapshot-aware collections

Compose provides an alternative to immutable collections: collections which actually send their own change notifications. These collections implement the standard Kotlin `Mutable*` interfaces, and can be changed in-place. When modified, they tell the snapshot state system that anything that read the collections should be invalidated and recalculated. This is possible because Compose's snapshot state system doesn't require state holders to have any explicit API for observing changes. For example, this is perfectly acceptable code:

```kotlin
val list: MutableList<String> = mutableStateListOf("a")
list.add("b)
```

Note that `list` is a `val`, not a `var`, because we only want to mutate the list _itself_, and not change the instance of the list to which the name `list` refers. Any code reading `list` in a Composable (or any other snapshot-aware context) will automatically be notified when the list is changed. Also note that `list` has type `MutableList` – aside from how the list is created, the `mutableStateListOf()` function, it's just a standard Kotlin list. For more information about how Compose's snapshot state system works, see [this blog post](https://blog.zachklipp.com/introduction-to-the-compose-snapshot-system/), as well as the [other posts in this series](https://blog.zachklipp.com/tag/compose-state-series/).

Under the hood, the mechanism by which these "snapshot state collections" work is actually simpler than you might suspect. They use immutable collections! `mutableStateListOf` returns a `MutableList` that effectively stores a private reference to a special _immutable_ list inside a `MutableState`. When you modify the list, it creates a copy of the private immutable list with the change and stores it in a `MutableState`. The trick here is that the immutable list isn't just a regular `ArrayList`, but an special implementation that is "persistent" – that's a fancy term from the world of functional programming that means that when the list is copied to be modified, only a small part of the list is actually _copied_, and most of the list ends up just being a reference to the old list. This means that even large collections can be copied and mutated without wasting space or time. Persistent data structures are a fun topic but too big to get into more here, so check out the [Wikipedia page](https://en.wikipedia.org/wiki/Persistent_data_structure) for more information. Kotlin doesn't include any persistent lists in its standard library, but does provide a separate kotlinx library, [`kotlinx.collections.immutable`](https://github.com/Kotlin/kotlinx.collections.immutable), which is actually what Compose uses.

## Conclusion

One last thing: This post isn't only relevant for collections or other "container" data structures, it applies to any classes you're using to hold state. Any class can have mutable properties (`var`s), and putting an instance of such a class into a mutable state holder has all the same issues as storing a mutable collection. If you're using Compose, you can use `MutableState`s to back those `var`s and still get change notifications, but, once again, any time you've got multiple ways to set the same value ("degree of mutability" higher than 1), it's confusing for readers of your code, users of your APIs, and asking for bugs.

If you came here wondering why your UI wasn't updating when you changed a list, I hope this post has not only answered your question, but also explained how to think about stateful data flow. Always think carefully about which parts of your data are mutable and immutable.

Please let me know if anything was unclear or if you have any further questions in the comments!

_Update: `BehaviorRelay` (and `BehaviorSubject`) aren't actually idempotent. Thanks for reminding me [@mzmzgreen](https://twitter.com/mzmzgreen)!_