Difference Between StateList and List<State> in Jetpack Compose

39 views Asked by At

Here is an example using StateList:

class GraphViewModel(application: Application): ViewModel() {

    private val _nodesState = mutableStateListOf<Node>()
    val nodesState: List<Node> = _nodesState

    init {
        repeat(30) {
            Node(
                id = it.toLong(),
                x = Random.nextDouble(300.0, 500.0),
                y = Random.nextDouble(400.0, 900.0)
            ).apply {
                _nodesState.add(this)
            }
        }

    }

    fun startOp() {
        thread(start = true) {
            viewModelScope.launch(Dispatchers.IO) {
                while (true) {
                    repeat(30) {
                        _nodesState[it] = _nodesState[it].copy(
                            x = Random.nextDouble(300.0, 500.0),
                            y = Random.nextDouble(400.0, 900.0)
                        )
                    }
                    delay(100)
                }
            }
        }
    }
}

@Composable
fun DrawNode(
    node: () -> Node,
) {
    Box(
        modifier = Modifier
            .offset {
                IntOffset(
                    x = (node().x - NODE_RADIUS).toInt(),
                    y = (node().y - NODE_RADIUS).toInt()
                )
            }
            .size(pixelToDp(px = NODE_RADIUS * 2))
    ) {
        Spacer(
            modifier = Modifier
                .offset { IntOffset((NODE_RADIUS / 2).toInt(), (NODE_RADIUS / 2).toInt()) }
                .size(pixelToDp(px = NODE_RADIUS))
                .clip(CircleShape)
                .background(color = MaterialTheme.colorScheme.onSecondary),
        )
    }
}

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewModel = GraphViewModel(application)
        setContent {
            NetWorkMemoTheme {
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    GraphScaffold {
                        viewModel.nodesState.forEach { node ->
                            key(node.id) {
                                DrawNode(
                                    node = { node },
                                    dragAble = true
                                )
                            }
                        }
                    }

                }
            }
        }
    }

    override fun onResume() {
        super.onResume()
        viewModel.startOp()
    }
}

I checked the recomposition count using LayoutInspector, and the results were as follows:

enter image description here

Here is an example using a list of States as elements

class GraphViewModel(application: Application): ViewModel() {

    private val _nodeStates = mutableListOf<MutableState<Node>>()
    val nodeStates: List<MutableState<Node>> = _nodeStates

    init {
        repeat(30) {
            Node(
                id = it.toLong(),
                x = Random.nextDouble(300.0, 500.0),
                y = Random.nextDouble(400.0, 900.0)
            ).apply {
                _nodesStates.add(mutableStateOf(this))
            }
        }

    }

    fun startOp() {
        thread(start = true) {
            viewModelScope.launch(Dispatchers.IO) {
                while (true) {
                    repeat(30) {
                        _nodeStates[it].value = _nodeStates[it].value.copy(
                            x = Random.nextDouble(300.0, 500.0),
                            y = Random.nextDouble(400.0, 900.0)
                        )
                    }
                    delay(100)
                }
            }
        }
    }
}

@Composable
fun DrawNode(
    node: () -> Node,
) {
    Box(
        modifier = Modifier
            .offset {
                IntOffset(
                    x = (node().x - NODE_RADIUS).toInt(),
                    y = (node().y - NODE_RADIUS).toInt()
                )
            }
            .size(pixelToDp(px = NODE_RADIUS * 2))
    ) {
        Spacer(
            modifier = Modifier
                .offset { IntOffset((NODE_RADIUS / 2).toInt(), (NODE_RADIUS / 2).toInt()) }
                .size(pixelToDp(px = NODE_RADIUS))
                .clip(CircleShape)
                .background(color = MaterialTheme.colorScheme.onSecondary),
        )
    }
}

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewModel = GraphViewModel(application)
        setContent {
            NetWorkMemoTheme {
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    GraphScaffold {
                        viewModel.nodeStates.forEach { nodeState ->
                            key(node.value.id) {
                                DrawNode(
                                    node = { node.value },
                                    dragAble = true
                                )
                            }
                        }
                    }

                }
            }
        }
    }

    override fun onResume() {
        super.onResume()
        viewModel.startOp()
    }
}

I checked the recomposition count using LayoutInspector, and the results were as follows: enter image description here

I'm having trouble understanding why recomposition still occurs in StateList even though I'm "deferring state reading" in composables. I also don't quite understand the difference between the two. Any advice would be greatly appreciated.

0

There are 0 answers