首页 > 解决方案 > 添加新项目时,Jetpack Compose 模型列表变得混乱

问题描述

我对 Jetpack compose 显示包含一系列ModelList项目的模型时遇到问题。添加新项目时,UI 元素的顺序会变得不正确。

这是一个非常简单CounterModel的包含 a ModelListof ItemModels:

@Model
data class CounterModel(
    var counter: Int = 0,
    var name: String = "",
    val items: ModelList<ItemModel> = ModelList()
)

@Model
data class ItemModel(
    var name: String
)

屏幕显示两个卡片行,每个ItemModelRowARowB。当我创建使用以下内容初始化的此屏幕时CounterModel

val model = CounterModel()
model.name="hi"
model.items.add(ItemModel("Item 1"))
model.items.add(ItemModel("Item 2"))
CounterModelScreen(model)

...它按预期显示如下:

项目 1 A 行

项目 1 B 行

项目 2 A 行

项目 2 B 行

当我单击“添加”按钮以插入新的ItemModel时,我只是希望看到

项目 3 A 行

项目 3 B 行

在底部。但是相反,顺序是混乱的,我看到两个 rowAs 然后是两个 rowB:

项目 1 A 行

项目 1 B 行

项目 2 A 行

项目 3 A 行

项目 3 B 行

项目 2 B 行

我真的不明白这怎么可能。UI 代码非常简单:循环遍历items并发出RowARowB为每个:

for (i in counterModel.items.indices) {
    RowA(counterModel, i)
    RowB(counterModel, i)
}

使用 Android Studio 4.0C6

这是完整的代码:

@Composable
fun CounterModelScreen(counterModel: CounterModel) {
    Column {
        TopAppBar(title = {
            Text(
                text = "Counter Model"
            )
        })

        CounterHeader(counterModel)
        for (i in counterModel.items.indices) {
            RowA(counterModel, i)
            RowB(counterModel, i)
        }
        Button(
            text = "Add",
            onClick = {
                counterModel.items.add(ItemModel("Item " + (counterModel.items.size + 1)))
            })
    }
}

@Composable
fun CounterHeader(counterModel: CounterModel) {
    Text(text = counterModel.name)
}

@Composable
fun RowA(counterModel: CounterModel, index: Int) {
    Padding(padding = 8.dp) {
        Card(color = Color.White, shape = RoundedCornerShape(4.dp)) {
            Column(crossAxisSize = LayoutSize.Expand) {
                Text(
                    text = counterModel.items[index].name
                )
                Text(text = "Row A")
            }
        }
    }
}

@Composable
fun RowB(counterModel: CounterModel, index: Int) {
    Padding(padding = 8.dp) {
        Card(color = Color.Gray, shape = RoundedCornerShape(4.dp)) {
            Column(crossAxisSize = LayoutSize.Expand) {
                Text(
                    text = counterModel.items[index].name
                )
                Text(text = "Row B")
            }
        }
    }
}

标签: androidkotlinandroid-jetpack-compose

解决方案


我已经使用compose-1.0.0-alpha07 对其进行了测试,并进行了一些更改以使代码适应更改后的 API。一切都完美无缺,所以我的猜测是,旧版本的 compose 中出现了一些问题,因为代码看起来正确并且在具有上述更改的更新版本中工作。

我还修改了您的代码以使用文档中推荐的状态,并添加了一个 ViewModel 来帮助您将视图与数据管理分离:

视图模型

class CounterModelViewModel : ViewModel() {
    private val myBaseModel = CounterModel().apply {
        name = "hi"
        items.add(ItemModel("Item 1"))
        items.add(ItemModel("Item 2"))
    }

    private val _modelLiveData = MutableLiveData(myBaseModel)
    val modelLiveData: LiveData<CounterModel> = _modelLiveData

    fun addNewItem() {
        val oldCounterModel = modelLiveData.value ?: CounterModel()
        // Items is casted to a new MutableList because the new state won't be notified if the new
        // counter model content is the same one as the old one. You can also change any other
        // properties instead like the name or the counter
        val newItemsList = oldCounterModel.items.toMutableList()
        newItemsList.add(ItemModel("Item " + (newItemsList.size + 1)))
        
        // Pass a new instance of CounterModel to the LiveData
        val newCounterModel = oldCounterModel.copy(items = newItemsList)
        _modelLiveData.value = newCounterModel
    }
}

可组合视图更新:

@Composable
fun CounterModelScreen(counterModel: CounterModel, onAddNewItem: () -> Unit) {
    ScrollableColumn {
        TopAppBar(title = {
            Text(
                text = "Counter Model"
            )
        })

        CounterHeader(counterModel)
        counterModel.items.forEachIndexed { index, item ->
            RowA(counterModel, index)
            RowB(counterModel, index)
        }
        Button(
            onClick = onAddNewItem
        ) {
            Text(text = "Add")
        }
    }
}

@Composable
fun CounterHeader(counterModel: CounterModel) {
    Text(text = counterModel.name)
}

@Composable
fun RowA(counterModel: CounterModel, index: Int) {

    Card(
        backgroundColor = Color.White,
        shape = RoundedCornerShape(4.dp),
        modifier = Modifier.padding(8.dp)
    ) {
        Column(modifier = Modifier.fillMaxWidth()) {
            Text(
                text = counterModel.items[index].name
            )
            Text(text = "Row A")
        }
    }

}

@Composable
fun RowB(counterModel: CounterModel, index: Int) {

    Card(
        backgroundColor = Color.Gray,
        shape = RoundedCornerShape(4.dp),
        modifier = Modifier.padding(8.dp)
    ) {
        Column(modifier = Modifier.fillMaxWidth()) {
            Text(
                text = counterModel.items[index].name
            )
            Text(text = "Row B")
        }
    }

}

前面的代码是从另一个包含 ViewModel 实例的可组合函数调用的,但是您可以将其更改为具有上述 ViewModel 实例的活动或片段,这取决于您的偏好。

@Composable
fun MyCustomScreen(viewModel: CounterModelViewModel = viewModel()) {
    val modelState: CounterModel by viewModel.modelLiveData.observeAsState(CounterModel())

    CounterModelScreen(
        counterModel = modelState,
        onAddNewItem = {
            viewModel.addNewItem()
        }
    )
}

推荐阅读