How to keep only one edit form open at a time in a composable function

Viewed 46

I am showing a list of rows with one word in it, inside a LazyColumn. On clicking the row, an edit form opens. The data is coming from a room database.

Since the row is on a separate composable function, I can open many different edit forms together (one in each row). But I want to show only one edit form in the whole list at a time. If I click one row to open an edit form, the rest of the open forms on the other rows should be closed. How can I do that?

Here is the code:

    val words: List<Word> by wordViewModel.allWords.observeAsState(listOf())

    var newWord by remember { mutableStateOf("") }
    val context = LocalContext.current
    val keyboardController = LocalSoftwareKeyboardController.current

        LazyColumn(
            modifier = Modifier
                .weight(1f)
                .padding(vertical = 16.dp),
            verticalArrangement = Arrangement.spacedBy(4.dp)
        ) {
            items(words) { word ->
                WordItemLayout(
                    word = word,
                    onSaveUpdatedWord = { onUpdateWord(it) },
                    onTrashClicked = { onDeleteWord(it) }
                )
            }
        }
@Composable
fun WordItemLayout(word: Word, onSaveUpdatedWord: (Word) -> Unit, onTrashClicked: (Word) -> Unit) {
    var showEditForm by remember { mutableStateOf(false) }
    var editedWord by remember { mutableStateOf(word.word) }
    val context = LocalContext.current

    Column {
        Row(
            modifier = Modifier
                .fillMaxWidth()
                .background(MaterialTheme.colors.primaryVariant)
                .padding(vertical = 12.dp, horizontal = 24.dp)
                .clickable {
                    showEditForm = !showEditForm
                    editedWord = word.word
                },
            verticalAlignment = Alignment.CenterVertically,
        ) {
            Image(painter = painterResource(R.drawable.ic_star), contentDescription = null)
            Text(
                text = word.word,
                color = Color.White,
                fontSize = 20.sp,
                modifier = Modifier
                    .padding(start = 16.dp)
                    .weight(1f)
            )
            // Delete Button
            IconButton(
                onClick = {
                    showEditForm = false
                    onTrashClicked(word)
                    Toast.makeText(context, "Word deleted", Toast.LENGTH_SHORT).show()
                },
                modifier = Modifier.size(12.dp)
            ) {
                Icon(
                    imageVector = Icons.Filled.Delete,
                    contentDescription = "Delete Word",
                    tint = Color.White
                )
            }
        }

        // word edit form
        if (showEditForm) {
            Row(
                modifier = Modifier.fillMaxWidth(),
                verticalAlignment = Alignment.Bottom
            ) {
                TextField(
                    value = editedWord,
                    onValueChange = { editedWord = it },
                    modifier = Modifier.weight(1f),
                    colors = TextFieldDefaults.textFieldColors(backgroundColor = Color.White) // TextField Background Color
                )
                // Update Button
                Button(
                    onClick = {
                        val updatedWord: Word = word

                        if (updatedWord.word != editedWord.trim()) {
                            updatedWord.word = editedWord.trim()
                            onSaveUpdatedWord(updatedWord)
                            Toast.makeText(context, "Word updated", Toast.LENGTH_SHORT).show()
                        }

                        showEditForm = false
                    },
                    modifier = Modifier.padding(start = 8.dp)
                ) {
                    Icon(imageVector = Icons.Filled.Done, contentDescription = "Update Word")
                }
            }
        }
    }
}

Thanks for your help!

1 Answers

An approach: In your view model, declare an openRowIndex state (this will store the index of the opened row, you can initialize it to -1 for example). Define a method that can change this state, for example updateOpenRowIndex

I'm not sure what kind of state holder you are using in your view model. I will use StateFlow for this answer. In your view model declare the new state and method:

private val _openRowIndex = MutableStateFlow(-1)
val openRowIndex: StateFlow<Int> = _openRowIndex

fun updateOpenRowIndex(updatedIndex: Int) {
   _openRowIndex.value = updatedIndex
}

For each row compisable, pass in the index of it inside the LazyColumn. You can get the indices using the itemsIndexed method. Also collect your openRowIndex, and pass that to the composable as well. Pass in also the method that updates the open row index:

itemsIndexed(words) { index, word ->
    //get the current opened row state and collect it (might look different for you if you are not using StateFlow):
    val openRowIndex = wordViewModel.openRowIndex.collectAsState()
    WordItemLayout(
        word = word,
        onSaveUpdatedWord = { onUpdateWord(it) },
        onTrashClicked = { onDeleteWord(it) },
        index = index, //new parameter!
        openRowIndex = openRowIndex.value //new parameter!
        onUpdateOpenedRow = wordViewModel::updateOpenRowIndex //new parameter!
    )

}

Now, in the row composable, simply check if the index and openRowIndex match, and display an opened row only if they match. Now to update the open row: make the Row clickable, and on click use view models updateOpenRowIndex method to update state to index. Compose will handle the rest and recompose when the state changes with the newly opened row!

fun WordItemLayout(
   word: Word,
   onSaveUpdatedWord: (Word) -> Unit,
   onTrashClicked: (Word), -> Unit,
   index: Int, //new parameters
   openRowIndex: Int,
   onUpdateOpenedRow: (Int) -> Unit
) {
   if(index == openRowIndex) {
      //display this row as opened
   } else {
      //display this row as closed
   }
}

As I said, make the row clickable and call the update function:

Row(
   modifier = Modifier.clickable {
      onUpdateOpenedRow(index)
      //additional instructions for what to happen when row is clicked...
   }
   //additional row parameters...
)
Related