I was creating a mutable state in Jetpack Compose using var state = mutableIntStateOf(n)
where n
is some integer. But when I tried to access the value of the integer from a composable function using state.value
, suddenly the Android Studio IDE gave the following warning:
Reading
value
will cause an autoboxing operation. UseintValue
to avoid unnecessary allocations.
How is state.value
different from state.intValue
? Does using one over another influence memory usage in a significant way?
This will indeed have a small impact on performance and memory consumption.
But first the full text of the warning:
Reading
value
will cause an autoboxing operation. UseintValue
to avoid unnecessary allocations.
Inspection info: Avoid using the genericvalue
property when using a specialized State type. Reading or writing to the state's genericvalue
property will result in an unnecessary autoboxing operation. Prefer the specialized value property (e.g.intValue
forMutableIntState
), or use property delegation to avoid unnecessary allocations.
Now, auto(un)boxing refers to the conversion of primitive data types to the object oriented world. The CPU can only work with primitive data types which is very fast. But in Kotlin everything is an object. To bridge that Kotlin boxes the primitve type in an object. To actually work with the data the value must be unboxed again.
Although not visible to the programmer, Kotlin actually can and work with primitive types under the hood and tries to do so as best as possible to prevent unnecessary boxing and unboxing, which would otherwise decrease performance and need additional memory for the boxed object. You just stumbled upon such an optimization where you prevented Kotlin from optimizing this (un)boxing.
If you would use mutableStateOf(0)
instead of mutableIntStateOf(0)
you would use the generic version of a MutableState that can only store objects. In that case everytime you read or write the state's value
property you would box respectively unbox the Int value. This is quite inefficient why you also get a warning in this situation:
Prefer
mutableIntStateOf
instead ofmutableStateOf
.
Inspection info: CallingmutableStateOf<T>()
whenT
is either backed by a primitive type on the JVM or is a value class results in a state implementation that requires all state values to be boxed. This usually causes an additional allocation for each state write, and adds some additional work to auto-unbox values when reading the value of the state. Instead, prefer to use a specialized primitive state implementation forInt
,Long
,Float
, andDouble
when the state does not need to track null values and does not override the defaultSnapshotMutationPolicy
. SeemutableIntStateOf()
,mutableLongStateOf()
,mutableFloatStateOf()
, andmutableDoubleStateOf()
for more information.
This warning probably let you to use mutableIntStateOf
in the first place. Which is good. But the MutableIntState
you get is also a simple, generic MutableState
. If you use the generic value
property you will force the optimized internal primitive type to be boxed, so it can fit through the value
property that can only work with objects. It will work, but then you wouldn't have needed the mutableIntStateOf
in the first place. Instead you should use the special intValue
property that only a MutableIntState
has. That provides direct access to the internal primitive type. To reiterate, this is invisible to the programmer, in both cases you will just see an Int
.
This may seem a bit complicated but as the initial warning also explained you can bypass all of that by delegating the State object:
var state by mutableIntStateOf(n)
The by
keyword unwraps the State object so you do not need to access its value property anymore because state
is now of type Int
and not MutableIntState
. This delegation also properly takes into account that it should use intValue
instead of value
under the hood. For more on property delegation see https://kotlinlang.org/docs/delegated-properties.html#local-delegated-properties.
That's what the warning you received is all about.
One last thing: Delegating like this may hide the State from the Compose runtime. This can occur if you placed that variable in a view model and update it from a coroutine that didn't originate in your composables. In that case your composables wouldn't trigger a recomposition. This is one of the issues when using MutableState in a view model why you shouldn't do that in the first place. Use MutableState only in your composables (and remember
it) and use MutableStateFlow in your view models.