I'm trying to achieve a fast image recomposition on a board when at least one variable from the objects in a 2d array changes.
I had a 2d array implemented as Array<Array<Cell>>
, yet, to achieve recomposition, I created a duplicate remember
variable on the Screen
. This worked fast enough, but it resulted in duplication of code from the business logic of the application and did not allow us to use all the functions, so it was not the right way.
Structure of date class Cell
:
data class Cell(
val x: Int,
val y: Int,
var isOpen: Boolean = false,
var index: Int = 0
)
Screen
code:
val row = 5
val col = 5
val board = viewModel.board.value
Column(Modifier.verticalScroll(rememberScrollState())) {
Row(Modifier.horizontalScroll(rememberScrollState())) {
Column {
for (i in 0 until row) {
Row {
for (j in 0 until col) {
val currentItem = remember(board) { mutableStateOf(board[i][j]) } //duplicate remember variable
Image(
painter = painterResource(
id = if (currentItem.value.isOpen) {
when (currentItem.value.index) {
0 -> R.drawable.ic_num_0
1 -> R.drawable.ic_num_1
2 -> R.drawable.ic_num_3
else -> R.drawable.ic_field
}
} else {
R.drawable.ic_field
}
),
contentDescription = null,
modifier = Modifier.clickable {
currentItem.value = currentItem.value.copy(isOpen = true) // duplication of code
board[i][j].isOpen = true // from the business logic of the application
}
)
}
}
}
}
}
}
ViewModel
code:
private val _board = mutableStateOf<Array<Array<Cell>>>(arrayOf())
val board: State<Array<Array<Cell>>> = _board
fun start() {
_board.value = createBoard()// Use case that returns Array(row) { i -> Array(col) { j -> Cell(i, j) } }
}
After that, I added a duplicate 2d array Array<Array<MutableState<Cell>>>
to the ViewModel
. Yet, to update it, I had to add two double loops — one to convert Array<Array<Cell>>
to Array<Array<MutableState<Cell>>>
and the other to convert Array<Array<MutableState<Cell>>>
back to Array<Array<Cell>>
. This helped to get rid of duplicate code on the Screen
and allowed us to implement all the functions, but predictably greatly increased the processing time for clicks.
Screen
code:
val row = 5
val col = 5
val board = viewModel.board.value
Column(Modifier.verticalScroll(rememberScrollState())) {
Row(Modifier.horizontalScroll(rememberScrollState())) {
Column {
for (i in 0 until row) {
Row {
for (j in 0 until col) {
val currentItem = remember(board) { viewModel.boardState[i][j] }
Image(
painter = painterResource(
id = if (currentItem.value.isOpen) {
when (currentItem.value.index) {
0 -> R.drawable.ic_num_0
1 -> R.drawable.ic_num_1
2 -> R.drawable.ic_num_3
else -> R.drawable.ic_field
}
} else {
R.drawable.ic_field
}
),
contentDescription = null,
modifier = Modifier.combinedClickable(
onClick = {
viewModel.updateBoard(
board[i][j],
Event.OnClick
)
}
)
)
}
}
}
}
}
}
ViewModel
code:
fun updateBoard(cell: Cell? = null, event: Event? = null) {
val newBoard = boardState.map { row -> row.map { item -> item.value }.toTypedArray() }.toTypedArray() // one double loops
_board.value = updateBoard(newBoard, cell, event) // Use case that returns Array(row) { i -> Array(col) { j -> Cell(i, j) } }
boardState = board.value.map { row -> row.map { item -> mutableStateOf(item) }.toTypedArray() }.toTypedArray() // two double loops
}
Question: How can I achieve fast recomposition when changing the variable of any object in a 2d array?
Note: In my project, I follow the principles of Clean Architecture, so I cannot use Array<Array<MutableState<Cell>>>
at the domain level, because MutableState
is part of the androidx
library.
Also, I want to add that when you click on a 2d field cell, other cells can change, and not just the one that was clicked.
Perhaps this can be solved using MutableStateFlow
, however, unlike MutableState
, it does not cause elements to be recomposed. Maybe there are other ways that I don't know about.
Update: I tried to cast Array<Array<Cell>>
to Array<Array<MutableStateFlow<Cell>>>
, and convert StateFlow
to Compose State
in Screen
using collectAsState()
, however this results in an error.
Screen
:
val board by viewModel.board
Column(Modifier.verticalScroll(rememberScrollState())) {
Row(Modifier.horizontalScroll(rememberScrollState())) {
Column {
for (i in 0 until row) {
Row {
for (j in 0 until col) {
val currentItem = remember(board) { board[i][j].collectAsState() } // error
...
ViewModel
:
private val _board = mutableStateOf<Array<Array<MutableStateFlow<Cell>>>>(arrayOf())
val board: State<Array<Array<MutableStateFlow<Cell>>>> = _board
fun updateBoard(cell: Cell? = null, event:Event? = null) {
_board.value = updateBoard(board.value, cell, event)
}
from Recomposing 2d array element when its variable changes in Compose with Clean Architecture
No comments:
Post a Comment