You might be creating your states wrong! — Jetpack Compose

Chetan Gupta
ProAndroidDev
Published in
5 min readOct 18, 2022

--

Don’t do this State Mistake in Jetpack/Jetbrain Compose — State Destructuring vs State Delegates.

Featured on : DroidCon | ProAndroidDev | Libhunt | AndroidWeekly | Commonware | AndroidStack | KotlinTrend

There are two ways to create a Mutable State in Jetpack/Jetbrain Compose. The first and my favorite way is by using Destructuring and another way is to use Delegate.

Modified Photo by Anders Norrback Bornholm on Unsplash

Mutable State with Destructuring 🔨

If you don’t know the concept of Destructuring in Kotlin Learn all about them from here :

If you look at the MutableState signature :

It has component1 and component2 operators which means, it can be destructed like :

val(count, setCount) = mutableStateOf(0)

Where count gets its value from component1 and setCount is a lambda coming from component2 .

Mutable State with Delegates 🍇

Learn delegate from the official channel :

https://kotlinlang.org/docs/delegated-properties.html

If you look into State and Mutable State documentation :

getValue(...) is defined over State and setValue(...) is defined over MutableState, thus we can use the concept of delegate over it example :

We control reading and writing on a state by defining val or var reference type of variable.

In my personal opinion using Destructuring way feels more readable and maintainable to me, as I’m aware of where the set operation has happened by tracking setCount(), and where the get operation is happening by tracking count. Whereas tracking state created from delegate you will have a hard time, also In my experience, I feel working with var ends up being exploited by developers easily.

Also in scenarios where I’m forwarding state to children composable, I don’t have to create in-place lambdas I can directly pass the reference, for instance :

Well until now I have been a fan of destructuring mutable states, but recently I have encountered a scenario that raised concern in my mind regarding this approach.

Consider the following code, I’m incrementing state by 1 when a button is clicked and displaying it on UI :

Output :

Button click increment state by 1 using state destructuring

I want to update the Snippet to increment by 2 instead of 1, see the following changes I have done :

Output :

Button click increment state by 2 using state destructuring

🤯 WTF — it didn’t work!

Let’s Investigate 🤔 why?

when you click on the button following is what is happening in the background :

but why??? setCount() should be up the mutable state right??

What broke?? is it Compose or MutableState?? :

Well, none is broken. I dig a bit deep into what happened and I figured out that :

  • Compose internally observe all reads and writes to state, we don’t need to understand inner working for this discussion, but you just need to know that compose works on a snapshot system, all the writes you do on mutable State effects this snapshot.
  • If there is a conflict between the previous and new snapshot then some action will happen to resolve the conflict. In terms of Compose Framework, it would lead to a trigger to re-composition.
  • So when the mutable state value is updated it would check the snapshot policy and validate if snapshot is having any conflict,
  • if the answer to that is yes, then the mutable state would inform compose to recompose and store the latest value, but instead of making the latest value an immediate state value, it would set it as a candidate value for the next recomposition cycle to read from.
  • Thus even if you have done multiple state updates, the last value you set would be treated as the latest candidate for the state value.
  • When recomposition would happen it reads the latest value from the mutable state and apply changes.

🤷‍♂️Okay… does that mean we have to wait until candidate value becomes real value i.e. till recomposition to update the state? i.e. doing multiple set State operations in a row is wrong in compose???

Well In most cases we can avoid this situation if we create a launchEffect and put our state change logic into that or refactor our logic to create only one set state.

but ideally, this isn't a behavior we expect from updating a state.

Let's see the same code using the delegate pattern :

Output :

Button click increment state by 2 using state delegation

this snippet would perform the operation correctly! 🤯. Why did this happen? doesn’t the same rule be applied to the behavior?

Well no! Creating State with Destructuring ≠ Creating State with Delegate

The internally same thing has happened, but when you use the state delegation method, whenever you are reading a value from the state it is delegating the operation of getting to MutableState.value, which returns candidate value instead of real value (if snapshot ids have changed).

But when you are working with Destructuring state, component1 has only called MutableState.value only once while destructuring and has set it to count variable i.e getter part. if you again read it from the getter it won't delegate the task to MutableState.value check and get the candidate value it would just return back last computed value when it was de-structuring.

that means while using Destructuring you have written code similar to :

Thus you have to wait for recomposition to re-initialize the value of the mutable state.

So actually no behavior of Compose or MutableState is broken, it is just the way destructuring and delegation work in the language.

Conclusion 💆🏻‍♀️

Though I still feel Destructuring is better than Delegating state but to work with the consistent state value it would be best to go with the Delegation path.

Thanks for reading this far. If you liked my article check out more of my article and content from https://chetangupta.net/

If you want me to write an article or train Android teams for your business do reach out by clicking 👉 here.

Until Next Time! Happy Hacking 👨🏻‍💻

P.S. if you liked the article don’t forget to clap and if you are a fan or want to support my content do consider dropping a tip/coffee/donation💰 here

--

--