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:
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:

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.
