When trying to edit my data (data:
var mutableTodosList = mutableStateOf(emptyList<ToDoItemData>())
var todosList: List<ToDoItemData> = mutableTodosList.value
init { // One-time initialization
mutableTodosList.value = listOf(
ToDoItemData(
1, "Example 1",
),
ToDoItemData(2, "Create a todo list"),
// ... more items
)
}
) in my app’s viewModel (app’s viewModel:
class ViewModelll: ViewModel() {
var mutableTodosList = mutableStateOf(emptyList<ToDoItemData>())
var todosList: List<ToDoItemData> = mutableTodosList.value
init { // One-time initialization
mutableTodosList.value = listOf(
ToDoItemData(
1, "Example 1",
),
ToDoItemData(2, "Create a todo list"),
// ... more items
)
}
fun addTodoItems(todoItems: List<ToDoItemData>) {
val newList = mutableTodosList.value.toMutableList().apply {
for (todoItem in todoItems) {
add(todoItem)
}
}
mutableTodosList.value = newList
}
fun modifyTodoItem(todoItemId: Int, todoItemParameterIndex: Int, modification: Any) {
val todoItem = findTodoItemById(todoItemId)
val newList = mutableTodosList.value.toMutableList().apply {
when (todoItemParameterIndex) {
0 -> this[indexOf(todoItem)].mainText = modification as String
12 -> this[indexOf(todoItem)].isChecked = modification as Boolean
else -> throw IllegalArgumentException("Invalid parameter index")
}
}
mutableTodosList.value = newList
}
fun removeTodoItem(todoItemId: Int) {
val newList = mutableTodosList.value.toMutableList().apply {
remove(findTodoItemById(todoItemId))
}
mutableTodosList.value = newList
}
}
)
with this code:
@Composable
fun GoalsScreen (navController: NavController) {
val viewModel: ViewModelll = viewModel()
Box(
modifier = Modifier.fillMaxSize()
) {
Column(
modifier = Modifier
.fillMaxSize(),
verticalArrangement = Arrangement.Top,
) {
ToDoList(
modifier = Modifier
.width(500.dp)
.height(200.dp),
todos = viewModel.mutableTodosList
)
Text(
"Click this to check",
modifier = Modifier.clickable {
viewModel.modifyTodoItem(2, 12, modification = !viewModel.mutableTodosList.value[1].isChecked)
}
)
Text(
"isChecked1: ${viewModel.mutableTodosList.value[0].isChecked}",
)
Text(
"isChecked2: ${viewModel.mutableTodosList.value[1].isChecked}",
)
}
}
}
@Composable
fun ToDoList(
modifier: Modifier,
todos: MutableState<List<ToDoItemData>> = remember { mutableStateOf(emptyList()) }
) {
Box(modifier = modifier) {
if (backgroundImage != null) {
Image(
modifier = Modifier.fillMaxSize(),
painter = backgroundImage,
contentDescription = null,
contentScale = ContentScale.Crop
)
}
LazyColumn(
modifier = Modifier.fillMaxSize()
) {
items(
todos.value
) { todo ->
ToDoItem(
todo = todo
)
}
}
}
}
@Composable
fun GlassToDoItem(
todo: ToDoItemData,
) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(vertical = 4.dp, horizontal = 4.dp)
.clip(RoundedCornerShape(16.dp))
.clickable { },
verticalArrangement = Arrangement.Top
) {
Row(
modifier = Modifier
.fillMaxSize(),
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically
) {
CircularCheckbox(
todo = todo,
modifier = Modifier
.size(45.dp)
.padding(10.dp)
)
Spacer(modifier = Modifier.width(5.dp))
Text(
text = todo.mainText,
fontSize = 17.sp
)
}
}
}
@Composable
fun ToDoItem(
todo: ToDoItemData,
) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(vertical = 4.dp, horizontal = 4.dp)
.clip(RoundedCornerShape(16.dp))
.background(Color.White)
.clickable { },
verticalArrangement = Arrangement.Top
) {
Row(
modifier = Modifier
.fillMaxSize(),
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically
) {
CircularCheckbox(
todo = todo,
modifier = Modifier
.size(45.dp)
.padding(10.dp)
)
Spacer(modifier = Modifier.width(5.dp))
Text(
text = todo.mainText,
fontSize = 17.sp
)
}
}
}
@Composable
fun CircularCheckbox(
todo: ToDoItemData,
modifier: Modifier,
animationDurationMiliseconds: Int = 700,
unCheckedCircleColor: Color = Color.Blue,
unCheckedLineSize: Dp = 2.dp,
checkedCircleColor: Color = Color.Blue,
checkedLineSize: Dp = 2.dp,
checkmarkSize: Dp = 20.dp,
checkmarkColor: Color = Color.Blue,
) {
val viewModel: ViewModelll = viewModel()
Box(modifier = modifier)
{
if (todo.isChecked) {
CircularCheckedCheckbox(modifier = Modifier
.fillMaxSize()
.clickable (
onClick = {
viewModel.modifyTodoItem(todo.id, 12, modification = !todo.isChecked)
}
),
circleColor = checkedCircleColor,
lineSize = checkedLineSize,
checkmarkSize = checkmarkSize,
checkmarkColor = checkmarkColor)
} else {
CircularUncheckedCheckbox(modifier = Modifier
.fillMaxSize()
.clickable (
onClick = {
viewModel.modifyTodoItem(todo.id, 12, modification = !todo.isChecked)
}
),
circleColor = unCheckedCircleColor,
lineSize = unCheckedLineSize)
}
}
}
@Composable
fun CircularCheckedCheckbox(
modifier: Modifier,
circleColor: Color = Color.Blue,
lineSize: Dp = 2.dp,
checkmarkSize: Dp = 20.dp,
checkmarkColor: Color = Color.Blue)
{
Canvas(modifier = modifier) {
val cSize = checkmarkSize.toPx()
val checkmark = Path().apply {
moveTo(cSize / 3f, cSize / 2f)
lineTo(cSize / 2f, cSize * 3f / 4f)
lineTo(cSize * 2f / 3f, cSize / 4f)
}
drawPath(
path = checkmark,
color = checkmarkColor
)
drawArc(
color = circleColor,
startAngle = 0f,
sweepAngle = 360f,
useCenter = true,
style = Stroke(width = lineSize.toPx())
)
}
}
@Composable
fun CircularUncheckedCheckbox(
modifier: Modifier,
circleColor: Color = Color.Blue,
lineSize: Dp = 2.dp)
{
Canvas(modifier = modifier) {
drawArc(
color = circleColor,
startAngle = 0f,
sweepAngle = 360f,
useCenter = true,
style = Stroke(width = lineSize.toPx())
)
}
}
data class ToDoItemData(
val id: Int,
var mainText: String,
var isChecked: Boolean = false,
)
The app does not update the isChecked value of the ToDoItemData
in the viewModel’s mutableTodosList
to its opposite. Therefor this also doesn’t update (re-compose) the UI to make the CircularCheckbox
change to a Checked or UnChecked version of itself, or so I think, from what I have observed trough:
Debugging:
When the CircularCheckedCheckbox
or CircularUnCheckedCheckbox
are clicked:
The value of the isChecked value does not change for the ToDoItemData
in the viewModel’s mutableTodosList
, however the isChecked value of todo of the CircularCheckbox
composable function does change, however this obviously doesn’t update the UI as the todo is not a mutableStateOf, unlike the mutableTodosList
from the viewModel which is.
Debugging trough code: I added some additional Text elements to show the state of the isChecked value in the app =
Text(
"Click this to check",
modifier = Modifier.clickable {
viewModel.modifyTodoItem(2, 12, modification = !viewModel.mutableTodosList.value[1].isChecked)
}
)
Text(
"isChecked1: ${viewModel.mutableTodosList.value[0].isChecked}",
)
Text(
"isChecked2: ${viewModel.mutableTodosList.value[1].isChecked}",
)
with this, the same result is show as in the debug.
While searching for a solution to this problem, I couldn’t find any answers on the web, and all AI LLMs also don’t know the answer.
I would highly appreciate a answer/fix to this problem, as it is a key bug in one of my projects.