How can I update a LazyColumn's listitems inside an AlertDialog from a ViewModel properly in Jetpack Compose? – Android

huangapple go评论60阅读模式
英文:

How can I update a LazyColumn's listitems inside an AlertDialog from a ViewModel properly in Jetpack Compose? - Android

问题

我正在尝试制作一个练习用的食谱应用程序,它可以将食谱和食材保存在 ROOM 数据库中。食谱在主屏幕上显示,并且有一个 FloatingActionButton,可以让您添加具有食材的新食谱。这个 FAB 打开一个 AlertDialog,您可以在其中设置新食谱的名称和描述,还可以添加食材。最初只有一个 AddIngredientRow(这是我为每个要添加到食谱中的食材制作的组合函数)。食材可以有一个“数量”、一个“单位”和一个“名称”,所以在单个 AddIngredientRow 上有 3 个文本字段。每当您按下 AlertDialog 上的“添加食材”按钮时,都会插入一个新行。

问题是我无法在这些文本字段中键入任何内容。每当我键入一些内容时,什么都不会发生,如果我添加一个新的“AddIngredientRow”,先前键入的值将只显示为单个字符。

在我的 ViewModel 中,我有一个 _addIngredients,以便我可以修改 AlertDialog 中的食材,以及一个 addIngredients 以将它们作为列表获取(我将此列表传递给 AlertDialog)。

private val _addIngredients = mutableStateListOf<Ingredient>()
val addIngredients: List<Ingredient> = _addIngredients

我已经尝试过更改这个,比如只传递一个 SnapshotStateList,将其作为 MutableStateFlow,并尝试其他方法。目前我使用 mutableStateListOf,因为这个可以在每次添加新的“AddIngredientRow”时更新 LazyColumn(不包括食材数据)。

这是我的 AlertDialog 如何工作的(我还尝试更改 LazyColumn 的 items 块为 itemsIndexed,并尝试其他方法将数据发送到“AddIngredientRow”):

@Composable
fun AddRecipeDialog(
    state: RecipeState,
    addIngredients: List<Ingredient>,
    onEvent: (RecipeEvent) -> Unit
) {
    AlertDialog(
        onDismissRequest = {
            onEvent(RecipeEvent.HideDialog)
        },
        title = {
            Text(...)
        },
        text = {
            Column(...) {
                OutlinedTextField(...) // 'Recipe name'
                OutlinedTextField(...) // 'Recipe description'
                Text(...) // 'Add ingredients'
                LazyColumn() {
                    if (addIngredients.isEmpty()) {
                        onEvent(
                            RecipeEvent.AddIngredient(
                                Ingredient(
                                    name = "",
                                    count = 0,
                                    unit = ""
                                )
                            )
                        )
                    }
                    items(addIngredients) { ingredient ->
                        val index = addIngredients.indexOf(ingredient)
                        AddIngredientRow(
                            modifier = Modifier.padding(bottom = 20.dp),
                            onCountChange = { newValue ->
                                try {
                                    onEvent(RecipeEvent.SetIngredientCount(index, newValue.toInt()))
                                } catch (e: NumberFormatException) {
                                    e.printStackTrace()
                                }
                            },
                            onUnitChange = { newValue ->
                                onEvent(RecipeEvent.SetIngredientUnit(index, newValue))
                            },
                            onNameChange = { newValue ->
                                onEvent(RecipeEvent.SetIngredientName(index, newValue))
                            },
                            ingredient = ingredient
                        )
                    }
                }
            }
        },
        confirmButton = {
            Button(
                modifier = Modifier
                    .fillMaxWidth(),
                onClick = {
                    onEvent(RecipeEvent.AddRecipe)
                }
            ) {
                Text(
                    text = stringResource(id = R.string.save)
                )
            }
        },
        dismissButton = {
            Button(
                modifier = Modifier
                    .fillMaxWidth(),
                onClick = {
                    onEvent(
                        RecipeEvent.AddIngredient(
                            Ingredient(
                                name = "",
                                count = 0,
                                unit = ""
                            )
                        )
                    )
                }
            ) {
                Text(
                    text = stringResource(R.string.add_ingredient)
                )
            }
        }
    )
}

这是 AddIngredientRow:

@Composable
fun AddIngredientRow(
    ingredient: Ingredient,
    onCountChange: (String) -> Unit,
    onUnitChange: (String) -> Unit,
    onNameChange: (String) -> Unit,
    modifier: Modifier = Modifier
) {
    Row(...) {
        OutlinedTextField(
            modifier = Modifier.weight(0.25f),
            value = ingredient.count.toString(),
            onValueChange = onCountChange,
            placeholder = {...},
            keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
        )
        OutlinedTextField(
            modifier = Modifier.weight(0.25f),
            value = ingredient.unit,
            onValueChange = onUnitChange,
            placeholder = {...}
        )
        OutlinedTextField(
            modifier = Modifier.weight(1f),
            value = ingredient.name,
            onValueChange = onNameChange,
            placeholder = {...}
        )
    }
}

这是 ViewModel 如何更改食材值的方式:

fun onEvent(event: RecipeEvent) {
        when(event) {
            // other events ...

            is RecipeEvent.SetIngredientCount -> {
                _addIngredients[event.index].count = event.count
            }
            is RecipeEvent.SetIngredientName -> {
                _addIngredients[event.index].name = event.name
            }
            is RecipeEvent.SetIngredientUnit -> {
                _addIngredients[event.index].unit = event.unit
            }
        }
    }

我猜想问题在于我在 TextFields 中使用的值实际上不是状态。我不知道如何正确从 ViewModel 获取数据并将更改发送回去。

我知道这可能有点奇怪,最好在新的独立屏幕中实现它,但这只是为了练习。

英文:

I'm trying to make a recipe app for practice that saves Recipes and Ingredients in a ROOM database. The recipes are shown in the main screen, and there is a FloatingActionButton that lets you add a new recipe with ingredients. This FAB opens an AlertDialog where you can set the name and the description of the new recipe and also add ingredients. Initially there is only 1 AddIngredientRow (which is a composable function that I made for each ingredient that I want to add for the recipe). An Ingredient can have a 'count', 'unit' and a 'name', so there are 3 textfields on a single AddIngredientRow. Whenever you press 'Add ingredient' on the AlertDialog a new row will be inserted.

The problem is that I can't type anything into those TextFields. Whenever I type something nothing happens and if I add a new 'AddIngredientRow' the previously typed values will show up but only as a single character.

In my ViewModel I have an _addIngredients so I can modify the ingredients in the AlertDialog, and an addIngredients to get them as a List (I pass this list to the AlertDialog)

private val _addIngredients = mutableStateListOf&lt;Ingredient&gt;()
val addIngredients: List&lt;Ingredient&gt; = _addIngredients

I've already tried changing that, like passing a SnapshotStateList only, making it as a MutableStateFlow and other stuff. I use mutableStateListOf for now because this one updates the LazyColumn every time I add a new 'AddIngredientRow' (without the ingredient data).

This is how my AlertDialog works (I also tried changing LazyColumn's items block to itemsIndexed, and other ways to send the data to the 'AddIngredientRow'):

@Composable
fun AddRecipeDialog(
state: RecipeState,
addIngredients: List&lt;Ingredient&gt;,
onEvent: (RecipeEvent) -&gt; Unit
) {
AlertDialog(
onDismissRequest = {
onEvent(RecipeEvent.HideDialog)
},
title = {
Text(...)
},
text = {
Column(...) {
OutlinedTextField(...) // &#39;Recipe name&#39;
OutlinedTextField(...) // &#39;Recipe description&#39;
Text(...) // &#39;Add ingredients&#39;
LazyColumn() {
if (addIngredients.isEmpty()) {
onEvent(
RecipeEvent.AddIngredient(
Ingredient(
name = &quot;&quot;,
count = 0,
unit = &quot;&quot;
)
)
)
}
items(addIngredients) { ingredient -&gt;
val index = addIngredients.indexOf(ingredient)
AddIngredientRow(
modifier = Modifier.padding(bottom = 20.dp),
onCountChange = { newValue -&gt;
try {
onEvent(RecipeEvent.SetIngredientCount(index, newValue.toInt()))
} catch (e: NumberFormatException) {
e.printStackTrace()
}
},
onUnitChange = { newValue -&gt;
onEvent(RecipeEvent.SetIngredientUnit(index, newValue))
},
onNameChange = { newValue -&gt;
onEvent(RecipeEvent.SetIngredientName(index, newValue))
},
ingredient = ingredient
)
}
}
}
},
confirmButton = {
Button(
modifier = Modifier
.fillMaxWidth(),
onClick = {
onEvent(RecipeEvent.AddRecipe)
}
) {
Text(
text = stringResource(id = R.string.save)
)
}
},
dismissButton = {
Button(
modifier = Modifier
.fillMaxWidth(),
onClick = {
onEvent(
RecipeEvent.AddIngredient(
Ingredient(
name = &quot;&quot;,
count = 0,
unit = &quot;&quot;
)
)
)
}
) {
Text(
text = stringResource(R.string.add_ingredient)
)
}
}
)
}

This is the AddIngredientRow:

@Composable
fun AddIngredientRow(
ingredient: Ingredient,
onCountChange: (String) -&gt; Unit,
onUnitChange: (String) -&gt; Unit,
onNameChange: (String) -&gt; Unit,
modifier: Modifier = Modifier
) {
Row(...) {
OutlinedTextField(
modifier = Modifier.weight(0.25f),
value = ingredient.count.toString(),
onValueChange = onCountChange,
placeholder = {...},
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
)
OutlinedTextField(
modifier = Modifier.weight(0.25f),
value = ingredient.unit,
onValueChange = onUnitChange,
placeholder = {...}
)
OutlinedTextField(
modifier = Modifier.weight(1f),
value = ingredient.name,
onValueChange = onNameChange,
placeholder = {...}
)
}
}

This is how the ViewModel changes the ingredient values:

fun onEvent(event: RecipeEvent) {
when(event) {
// other events ...
is RecipeEvent.SetIngredientCount -&gt; {
_addIngredients[event.index].count = event.count
}
is RecipeEvent.SetIngredientName -&gt; {
_addIngredients[event.index].name = event.name
}
is RecipeEvent.SetIngredientUnit -&gt; {
_addIngredients[event.index].unit = event.unit
}
}
}

I'm guessing that the problem is the values I'm using in the TextFields are not really states. I don't know how to get the data properly from the ViewModel and also send the changes back.

I know this might be weird, probably would be better implemented in a new separate screen or something, but it's just for practice.

答案1

得分: 0

我找到了一个解决方案。我创建了一个IngredientState类,其中包含3个MutableStates作为值,因此UI实际上可以将它们视为状态。我不知道这有多优化,但现在它可以工作了。

在ViewModel中:

val addIngredients = mutableStateListOf<IngredientState>()

IngredientState:

data class IngredientState(
    val name: MutableState<String>,
    val count: MutableState<String>,
    val unit: MutableState<String>
)
英文:

I found a solution. I made an IngredientState class that has the 3 values as MutableStates, so the UI actually can handle them as states. I don't know how optimal is this, but it's working now.

In the viewmodel:

val addIngredients = mutableStateListOf&lt;IngredientState&gt;()

IngredientState:

data class IngredientState(
val name: MutableState&lt;String&gt;,
val count: MutableState&lt;String&gt;,
val unit: MutableState&lt;String&gt;
)

huangapple
  • 本文由 发表于 2023年7月6日 22:38:31
  • 转载请务必保留本文链接:https://go.coder-hub.com/76629981.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定