首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >Jetpack组合状态:修改类属性

Jetpack组合状态:修改类属性
EN

Stack Overflow用户
提问于 2020-09-18 12:46:07
回答 4查看 8.7K关注 0票数 16

下面两个示例简单地将'a‘添加到给定的默认值中。所使用的compose_version1.0.0-alpha03,这是最新的(据我所知)。

这个例子与我在研究中发现的大多数例子非常相似。

示例1

代码语言:javascript
运行
复制
@Composable
fun MyScreen() {
    val (name, setName) = remember { mutableStateOf("Ma") }

    Column {
        Text(text = name) // 'Ma'
        Button(onClick = {
                setName(name + "a") // change it to 'Maa'
        }) {
            Text(text = "Add an 'a'")
        }
    }
}

然而,这并不总是切合实际的。例如,数据比单个字段更复杂。例如,一个类,甚至一个Room data class

示例2

代码语言:javascript
运行
复制
// the class to be modified
class MyThing(var name: String = "Ma");


@Composable
fun MyScreen() {
    val (myThing, setMyThing) = remember { mutableStateOf(MyThing()) }

    Column {
        Text(text = myThing.name) // 'Ma'
        Button(onClick = {
                var nextMyThing = myThing
                nextMyThing.name += "a" // change it to 'Maa'
                setMyThing(nextMyThing)
        }) {
            Text(text = "Add an 'a'")
        }
    }
}

当然,示例1可以工作,但是示例2不能工作。对于我来说,这是一个简单的错误,还是我忽略了应该如何修改这个类实例的更大的图景?

编辑:

我已经找到了一种方法来完成这个任务,但是它看起来效率很低。然而,它确实与React管理状态的方式相一致,因此它可能是正确的方法。

示例2中的问题非常清楚地是,myNextThing不是原始myThing的副本,而是对它的引用。就像React一样,Jetpack撰写在修改状态时似乎需要一个全新的对象。这可以通过以下两种方式之一实现:

  1. 创建MyThing类的新实例,更改需要更改的内容,然后使用新的类实例调用setMyThing()
  2. class MyThing更改为data class MyThing,并使用copy()函数创建具有相同属性的新实例。然后,更改所需的属性并调用setMyThing()。在我的问题中,这是最好的方法,因为我明确表示,我想用它来修改Android使用的给定data class上的数据。

示例3 (函数式)

代码语言:javascript
运行
复制
// the class to be modified
data class MyThing(var name: String = "Ma");


@Composable
fun MyScreen() {
    val (myThing, setMyThing) = remember { mutableStateOf(MyThing()) }

    Column {
        Text(text = myThing.name) // 'Ma'
        Button(onClick = {
                var nextMyThing = myThing.copy() // make a copy instead of a reference
                nextMyThing.name += "a" // change it to 'Maa'
                setMyThing(nextMyThing)
        }) {
            Text(text = "Add an 'a'")
        }
    }
}
EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2020-09-20 20:31:58

实际上,在我看来,最好的方法是复制()一个数据类。

在使用自定义remember()的特定情况下,这可能确实是最好的选择,尽管可以通过在copy()函数上使用命名参数来做到这一点:

代码语言:javascript
运行
复制
// the class to be modified
data class MyThing(var name: String = "Ma", var age: Int = 0)

@Composable
fun MyScreen() {
  val (myThing, myThingSetter) = remember { mutableStateOf(MyThing()) }

  Column {
    Text(text = myThing.name)
    // button to add "a" to the end of the name
    Button(onClick = { myThingSetter(myThing.copy(name = myThing.name + "a")) }) {
      Text(text = "Add an 'a'")
    }
    // button to increment the new "age" field by 1
    Button(onClick = { myThingSetter(myThing.copy(age = myThing.age + 1)) }) {
      Text(text = "Increment age")
    }
  }
}

但是,我们仍将更新视图模型并观察它们的结果(LiveDataStateFlow、RxJava Observable等)。我预计remember { mutableStateOf() }将在本地用于尚未准备好提交到视图模型但需要多个用户输入的数据,因此需要将其表示为状态。你是否觉得你需要一个data class,这取决于你自己。

对于我来说,这是一个简单的错误,还是我忽略了应该如何修改这个类实例的更大的图景?

Compose无法知道对象发生了变化,因此它不知道需要重新组合。

总体来说,合成是围绕着对不可变数据流的反应而设计的。remember { mutableStateOf() }创建本地流。

然而,另一种办法将是最受欢迎的。

您不局限于单个remember

代码语言:javascript
运行
复制
@Composable
fun MyScreen() {
  val name = remember { mutableStateOf("Ma") }
  val age = remember { mutableStateOf(0) }

  Column {
    Text(text = name.value)
    // button to add "a" to the end of the name
    Button(onClick = { name.value = name.value + "a"}) {
      Text(text = "Add an 'a'")
    }
    // button to increment the new "age" field by 1
    Button(onClick = { age.value = age.value + 1 }) {
      Text(text = "Increment age")
    }
  }
}
票数 10
EN

Stack Overflow用户

发布于 2021-07-15 16:47:58

好的,对于任何想知道这个问题的人来说,有一个更简单的方法来解决这个问题。当您定义如下可变状态属性时:

代码语言:javascript
运行
复制
//There is a second paremeter wich defines the policy of the changes on de state if you
//set this value to neverEqualPolicy() you can make changes and then just set the value
class Vm : ViewModel() {
 val dummy = mutableStateOf(value = Dummy(), policy= neverEqualPolicy())

 //Update the value like this 
 fun update(){
 dummy.value.property = "New value"
//Here is the key since it has the never equal policy it will treat them as different no matter the changes
 dummy.value = dummy.value
 }
}

有关可用策略的更多信息:https://developer.android.com/reference/kotlin/androidx/compose/runtime/SnapshotMutationPolicy

票数 10
EN

Stack Overflow用户

发布于 2020-09-18 14:56:07

实际上,在我看来,实现这一点的最佳方法是copy() a data class

使用反射(允许修改不同类型的属性)的完整和有用的示例如下所示:

代码语言:javascript
运行
复制
// the class to be modified
data class MyThing(var name: String = "Ma", var age: Int = 0);


@Composable
fun MyScreen() {
    val (myThing, setMyThing) = remember { mutableStateOf(MyThing()) }

    // allow the `onChange()` to handle any property of the class
    fun <T> onChange(field: KMutableProperty1<MyThing, T>, value: T) {
        // copy the class instance
        val next = myThing.copy()
        // modify the specified class property on the copy
        field.set(next, value)
        // update the state with the new instance of the class
        setMyThing(next)
    }

    Column {
        Text(text = myThing.name)
        // button to add "a" to the end of the name
        Button(onClick = { onChange(MyThing::name, myThing.name + "a") }) {
            Text(text = "Add an 'a'")
        }
        // button to increment the new "age" field by 1
        Button(onClick = { onChange(MyThing::age, myThing.age + 1) }) {
            Text(text = "Increment age")
        }
    }
}

虽然每次单击按钮时实例化类的副本(或者在实际使用的用例中按下键盘时使用TextField而不是按钮)对于较大的类来说可能有点浪费,但一般情况下,复合框架似乎更喜欢这种方法。如前所述,这与做出反应的方式是一致的:状态从来不被修改或附加,它总是被完全替换。

然而,另一种办法将是最受欢迎的。

票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/63956058

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档