Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Jetpack Compose @Stable List<T> parameter recomposition

@Composable functions are recomposed

  • if one the parameters is changed or
  • if one of the parameters is not @Stable/@Immutable

When passing items: List<Int> as parameter, compose always recomposes, regardless of List is immutable and cannot be changed. (List is interface without @Stable annotation). So any Composable function which accepts List<T> as parameter always gets recomposed, no intelligent recomposition.

How to mark List<T> as stable, so compiler knows that List is immutable and function never needs recomposition because of it?

Only way i found is wrapping like @Immutable data class ImmutableList<T>(val items: List<T>). Demo (when Child1 recomposes Parent, Child2 with same List gets recomposed too):

class TestActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ComposeBasicsTheme {
                Parent()
            }
        }
    }
}

@Composable
fun Parent() {
    Log.d("Test", "Parent Draw")
    val state = remember { mutableStateOf(false) }
    val items = remember { listOf(1, 2, 3) }

    Column {
        // click forces recomposition of Parent
        Child1(value = state.value,
            onClick = { state.value = !state.value })

        //
        Child2(items)
    }
}

@Composable
fun Child1(
    value: Boolean,
    onClick: () -> Unit
) {
    Log.d("Test", "Child1 Draw")
    Text(
        "Child1 ($value): Click to recompose Parent",
        modifier = Modifier
            .clickable { onClick() }
            .padding(8.dp)
    )
}

@Composable
fun Child2(items: List<Int>) {
    Log.d("Test", "Child2 Draw")
    Text(
        "Child 2 (${items.size})",
        modifier = Modifier
            .padding(8.dp)
    )
}
like image 502
Jemshit Iskenderov Avatar asked Feb 14 '26 08:02

Jemshit Iskenderov


2 Answers

You mainly have 2 options:

  1. Use a wrapper class annotated with either @Immutable or @Stable (as you already did).
  2. Compose compiler v1.2 added support for the Kotlinx Immutable Collections library.

With Option 2 you just replace List with ImmutableList. Compose treats the collection types from the library as truly immutable and thus will not trigger unnecessary recompositions.

Please note: At the time of writing this, the library is still in alpha.

I strongly recommend reading this article to get a good grasp on how compose handles stability (plus how to debug stability issues).

like image 132
Hanan Rofe Haim Avatar answered Feb 15 '26 20:02

Hanan Rofe Haim


Just to add to the answers regarding wrapping the collection in a wrapper class and marking as @Stable or @Immutable, you can add by to use delegation. Delegating to the wrapped collection can be handy if you want to be able to access the collection without directly referencing it:

/**
 * List wrapper for Composable performance optimization. Uses delegation for read only list operations.
 */
@Immutable
data class ImmutableList<T>(
    val wrapped: List<T> = listOf()
) : List<T> by wrapped

The wrapper can then be used as though it were the list itself:

val myList = ImmutableList<SomeClass>(listOf(...))
val firstValue = myList.first()
val lastValue = myList.last()
val size = myList.size
like image 37
VIN Avatar answered Feb 15 '26 21:02

VIN