模型中列表的值已更新,但未反映可组合函数

时间:2019-12-01 12:46:09

标签: android android-jetpack android-jetpack-compose

我正在创建将jetpack与mvvm一起使用的演示项目,我创建了包含用户列表的模型类。这些用户显示在列表中,并且顶部有一个按钮,单击该按钮会将新用户添加到列表中... 当用户单击按钮时,lambda更新有关其的活动,活动调用viewmodel,该活动将数据添加到列表中并使用livedata更新回活动,现在,在模型接收到新数据之后,它不会更新有关它的可组合函数,因此也不会更新列表未更新。 这是代码

@Model
data class UsersState(var users: ArrayList<UserModel> = ArrayList())

活动

class MainActivity : AppCompatActivity() {
    private val usersState: UsersState = UsersState()
    private val usersListViewModel: UsersListViewModel = UsersListViewModel()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        usersListViewModel.getUsers().observe(this, Observer {
            usersState.users.addAll(it)
        })
        usersListViewModel.addUsers()
        setContent {
            UsersListUi.addList(
                usersState,
                onAddClick = { usersListViewModel.addNewUser() },
                onRemoveClick = { usersListViewModel.removeFirstUser() })
        }
    }

}

ViewModel

class UsersListViewModel {

    private val usersList: MutableLiveData<ArrayList<UserModel>> by lazy {
        MutableLiveData<ArrayList<UserModel>>()
    }
    private val users: ArrayList<UserModel> = ArrayList()
    fun addUsers() {
        users.add(UserModel("jon", "doe", "android developer"))
        users.add(UserModel("john", "doe", "flutter developer"))
        users.add(UserModel("jonn", "dove", "ios developer"))
        usersList.value = users
    }

    fun getUsers(): MutableLiveData<ArrayList<UserModel>> {
        return usersList
    }

    fun addNewUser() {
        users.add(UserModel("jony", "dove", "ruby developer"))
        usersList.value = users
    }

    fun removeFirstUser() {
        if (!users.isNullOrEmpty()) {
            users.removeAt(0)
            usersList.value = users
        }
    }
}

可组合功能

@Composable
    fun addList(state: UsersState, onAddClick: () -> Unit, onRemoveClick: () -> Unit) {
        MaterialTheme {
            FlexColumn {
                inflexible {
                    // Item height will be equal content height
                    TopAppBar( // App Bar with title
                        title = { Text("Users") }
                    )
                    FlexRow() {
                        expanded(flex = 1f) {
                            Button(
                                text = "add",
                                onClick = { onAddClick.invoke() },
                                style = OutlinedButtonStyle()
                            )

                        }
                        expanded(flex = 1f) {
                            Button(
                                text = "sub",
                                onClick = { onRemoveClick.invoke() },
                                style = OutlinedButtonStyle()
                            )
                        }
                    }
                    VerticalScroller {
                        Column {
                            state.users.forEach {
                                Column {
                                    Row {
                                        Text(text = it.userName)
                                        WidthSpacer(width = 2.dp)
                                        Text(text = it.userSurName)
                                    }
                                    Text(text = it.userJob)

                                }
                                Divider(color = Color.Black, height = 1.dp)

                            }
                        }
                    }
                }

            }

        }
    }

整个源代码可用here

我不确定我是在做错什么,还是因为jetpack compose仍在开发人员预览中,所以将不胜感激。 谢谢

2 个答案:

答案 0 :(得分:4)

嗨!

此处来自Android Devrel的Sean。不会更新的主要原因是UserState.users中的ArrayList是不可观察的–它只是常规的ArrayList,因此进行变异不会更新compose。

Model使模型类的所有属性均可观察到

这似乎可行,因为UserState被注释为@Model,这使得Compose可以自动观察事物。但是,可观察性仅适用于一级深度。这是一个永远不会触发重组的示例:

class ModelState(var username: String, var email: String)

@Model
class MyImmutableModel(val state: ModelState())

由于state变量是不可变的(val),因此当您更改emailusername时,Compose将永远不会触发重组。这是因为@Model仅适用于带注释的类的属性。在此示例中,state在Compose中是可观察到的,但是usernameemail只是常规字符串。

修复选项#0:您不需要 @Model

在这种情况下,您已经有LiveData中的getUsers() –您可以在撰写中看到这一点。我们尚未在开发版本中发布Compose观察,但是可以使用效果编写一个观察,直到我们发布观察方法为止。只要记住要删除onDispose {}中的观察者即可。

如果您使用任何其他可观察类型,例如FlowFlowable等,也是如此。您可以将它们直接传递到@Composable函数中,并观察其效果而无需引入中间@Model类。

修复选项1:在@Model中使用不可变类型

许多开发人员更喜欢UI状态的不可变数据类型(像MVI这样的模式鼓励这样做)。您可以更新示例以使用不可变列表,然后为了更改列表,必须将其分配给users属性,Compose可以观察到。

@Model
class UsersState(var users: List<UserModel> = listOf())

然后,当您要更新它时,必须分配users变量:

val usersState = UsersState()

// ...
fun addUsers(newUsers: List<UserModel>) {
    usersState.users = usersState.users + newUsers 
    // performance note: note this allocates a new list every time on the main thread
    // which may be OK if this is rarely called and lists are small
    // it's too expensive for large lists or if this is called often
}

每次将新的List<UserModel分配给users时,这总是会触发重新组合,并且由于在分配列表后无法编辑列表,因此UI始终会显示当前状态。

在这种情况下,由于数据结构是List,因此您不能将不可变类型的性能串联起来。但是,如果您持有一个不变的data class,则此选项是一个不错的选择,因此出于完整性考虑,我将其包括在内。

修复选项2:使用ModelList

Compose针对此用例具有特殊的 observable 列表类型。您可以使用ArrayList代替,而列表的任何更改都可以通过撰写观察。

@Model
class UsersState(val users: ModelList<UserModel> = ModelList())

如果您使用ModelList,则您在“活动”中编写的其余代码将正常运行,并且Compose将能够直接观察对users的更改。

相关:嵌套@Model类

值得注意的是,您可以嵌套@Model类,这是ModelList版本的工作方式。回到开头的示例,如果将两个类都注释为@Model,则所有属性都将在Compose中可见。

@Model
class ModelState(var username: String, var email: String)

@Model
class MyModel(var state: ModelState())

注意:此版本将@Model添加到ModelState,并且还允许在MyModel

中重新分配状态

由于@Model使得可以通过compose观察到该类的所有属性,因此stateusernameemail都可以观察。

TL; DR选择哪个选项

在此代码中完全避免使用@Model(选项#0)将避免引入仅用于Compose的重复模型层。由于您已经将状态保持在ViewModel中并通过LiveData进行了公开,因此您可以直接将LiveData传递给它,以便在那里进行观察。这将是我的首选。

如果您要使用@Model表示可变列表,请使用选项#2中的ModelList


您可能需要更改ViewModel,以同时容纳MutableLiveData引用。当前,ViewModel拥有的列表是不可观察的。有关Android体系结构组件中的ViewModelLiveData的介绍,请查看Android Basics course

答案 1 :(得分:0)

未观察到您的模型,因此不会反映出更改。 In this article在“全部放在一起”部分下,添加了列表。

from collections import defaultdict
# convert a dictionary to a defaultdict
d = defaultdict(lambda: None,d)

列表示例:

<ListView x:Name="corsiList" HasUnevenRows="True" SeparatorVisibility="None">
         <ListView.ItemTemplate>
              <DataTemplate>
                   <ViewCell>
                       <ctrls:InfoCorso Title="{Binding CorsiList.Nome}" Time="18:30" StyleColor="{Binding ButtonColor}">
                            <x:Arguments>
                                 <x:Int32>Binding Subs</x:Int32>
                                 <x:Int32>Binding Max</x:Int32>
                            </x:Arguments>
                       </ctrls:InfoCorso>
                   </ViewCell>
              </DataTemplate>
         </ListView.ItemTemplate>
</ListView>