【问题标题】:value of list in model is updated but it does not reflect on composable function模型中列表的值已更新,但未反映可组合功能
【发布时间】:2019-12-01 12:46:09
【问题描述】:

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

@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() })
        }
    }

}

视图模型

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 仍处于开发人员预览版中,因此不胜感激。 谢谢

【问题讨论】:

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


    【解决方案1】:

    喂!

    来自 Android Devrel 的肖恩在这里。未更新的主要原因是 UserState.users 中的 ArrayList 不可观察 - 它只是一个常规的 ArrayList,因此对其进行变异不会更新 compose。

    模型使 模型类的所有属性都可观察

    这似乎可行,因为 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

    在这种情况下,您已经拥有来自 getUsers()LiveData - 您可以在 compose 中观察到它。我们还没有在开发版本中发布 Compose 观察,但是在我们发布观察方法之前,可以使用效果编写一个。只要记住删除onDispose {} 中的观察者即可。

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

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

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

    @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&lt;UserModel 分配给users,这将始终触发重组,并且由于在分配列表后无法编辑列表,因此 UI 将始终显示当前状态。

    在这种情况下,由于数据结构是List,因此您连接不可变类型的性能可能无法接受。但是,如果您持有不可变的 data class,则此选项是一个不错的选项,因此为了完整性,我将其包括在内。

    修复选项 #2:使用 ModelList

    Compose 有一个特殊的 observable 列表类型,正好适合这个用例。您可以使用 ArrayList 代替 ArrayList,并且列表的任何更改都可以通过 compose 观察到。

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

    如果您使用ModelList,您在 Activity 中编写的其余代码将正常工作,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

    【讨论】:

      【解决方案2】:

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

      val list = +memo{ calculation: () -> T}
      

      您的列表示例:

      @Composable
      fun test(supplier: UserState) {
         val list = +memo{supplier.users}
         ListConsumer(list){
             /* Do other stuff for your usecase */
         }
      }
      

      【讨论】:

      • 如果您想更改 UserModel 的值,您可能需要将 @Model 注释添加到此数据类
      • 我的包含 userModels 列表的 usersState 类已经用 @model 进行了注释,根据您的建议,我尝试将上面的代码与 +memo 一起使用,但仍然遇到同样的问题
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-04-27
      • 2022-06-11
      • 1970-01-01
      • 2021-12-18
      • 2022-01-16
      相关资源
      最近更新 更多