Search code examples
androidkotlinandroid-jetpack-composeautoboxing

Jetpack Compose - Why does reading the value of a MutableIntState cause autoboxing operation?


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. Use intValue to avoid unnecessary allocations.

How is state.value different from state.intValue? Does using one over another influence memory usage in a significant way?


Solution

  • 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. Use intValue to avoid unnecessary allocations.
    Inspection info: Avoid using the generic value property when using a specialized State type. Reading or writing to the state's generic value property will result in an unnecessary autoboxing operation. Prefer the specialized value property (e.g. intValue for MutableIntState), 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 of mutableStateOf.
    Inspection info: Calling mutableStateOf<T>() when T 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 for Int, Long, Float, and Double when the state does not need to track null values and does not override the default SnapshotMutationPolicy. See mutableIntStateOf(), mutableLongStateOf(), mutableFloatStateOf(), and mutableDoubleStateOf() 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.