Jetpack Compose State:修改类属性

nba*_*man 8 android state kotlin android-jetpack android-jetpack-compose

下面的两个示例简单地将“a”添加到给定的默认值。该compose_version使用的1.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'")
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

然而,这并不总是实用的。例如,数据比单个字段更复杂。例如一个类,甚至一个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'")
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

当然,示例 1有效,但示例 2无效。这是我的一个简单错误,还是我错过了关于我应该如何修改这个类实例的大图?

编辑:

有点找到了一种方法,使这项工作,但似乎效率不高。然而,它确实与 React 管理状态的方式一致,所以也许这是正确的方法。

示例 2 中的问题很明显myNextThing不是原始 的副本myThing,而是对它的引用。就像 React 一样,Jetpack Compose 在修改状态时似乎想要一个全新的对象。这可以通过以下两种方式之一完成:

  1. 创建类的新实例MyThing,更改需要更改的内容,然后setMyThing()使用新的类实例进行调用
  2. 更改class MyThingdata class MyThing和使用copy()功能来创建具有相同属性的新实例。然后,更改所需的属性并调用setMyThing()。鉴于我明确表示我想用它来修改data classAndroid Room 使用的给定数据,这是我问题上下文中的最佳方法。

示例 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'")
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Aug*_*nso 12

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

//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
 }
}
Run Code Online (Sandbox Code Playgroud)

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


Com*_*are 11

事实上,在我看来,解决此问题的最佳方法是 copy() 数据类。

remember()在使用custom 的特定情况下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")
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

但是,我们仍将更新视图模型并观察它们的结果(LiveDataStateFlow、 RxJavaObservable等)。我希望它将remember { mutableStateOf() }在本地用于尚未准备好提交到视图模型但需要多位用户输入的数据,因此需要表示为状态。您是否觉得需data class要这样做取决于您。

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

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

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

然而,另一种方法将受到欢迎。

您不限于单一remember

@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")
    }
  }
}
Run Code Online (Sandbox Code Playgroud)