Jetpack撰写状态:修改类属性

时间:2020-09-18 12:46:07

标签: android kotlin state android-jetpack android-jetpack-compose

下面的两个示例只是将“ a”添加到给定的默认值。据我所知,使用的compose_version1.0.0-alpha03,这是最新的日期。

该示例与我在研究过程中发现的大多数示例最为相似。

示例1

@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

// 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 Compose在修改状态时似乎想要一个全新的对象。可以通过以下两种方式之一完成此操作:

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

示例3 (功能性)

// 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'")
        }
    }
}

3 个答案:

答案 0 :(得分:2)

实际上,在我看来,实现此目的的最佳方法是copy()一个数据类。

在使用自定义remember()的{​​{1}}的特定情况下,虽然可以通过在data class函数上使用命名参数来更简洁地完成操作,但这确实是最好的选择:

copy()

但是,我们仍将更新视图模型并观察它们的结果(// 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") } } } LiveData,RxJava StateFlow等)。我希望Observable将在本地用于尚未准备好提交给视图模型但需要多位用户输入的数据,因此需要将其表示为状态。是否觉得自己需要remember { mutableStateOf() }取决于您自己。

这是我一个简单的错误,还是我错过了有关如何修改此类实例的大图?

Compose无法知道对象已更改,因此也不知道需要重新组合。

总体而言,Compose的设计宗旨是对不可变数据流做出反应。 data class创建本地流。

但是,最欢迎使用替代方法。

您不仅限于一个remember { mutableStateOf() }

remember

答案 1 :(得分:1)

实际上,在我看来,实现此目的的最佳方法是copy()data class

使用反射的完整而有用的示例(以允许修改我不同类型的属性可能看起来像这样:

// 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而不是按钮来按下键盘时实例化状态的类的副本)可能是对于较大的类而言有点浪费,通常似乎Compose框架更喜欢这种方法。如前所述,这与React的工作方式一致:状态永远不会被修改或附加,它总是被完全替换。

但是,最欢迎使用替代方法。

答案 2 :(得分:1)

好的,对于任何对此感到疑惑的人,有一种更简单的方法可以解决此问题。当您像这样定义可变状态属性时:

//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