You might be creating your states wrong! — Jetpack Compose
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
.
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 :
I want to update the Snippet to increment by 2 instead of 1, see the following changes I have done :
Output :
🤯 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 :
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