英文:
How can I use destructuring declarations in a class in Kotlin?
问题
Code A:
目前,我在我的项目中使用 Code A,我认为它太复杂了。
但是 Code B 出现以下错误
“仅允许对局部变量/值进行解构声明”
我该如何优化 Code A?
**Code A**
```kotlin
class PreferenceParameterState private constructor(context: Context) {
var openEditDialog by mutableStateOf(false)
private set
fun set_OpenEditDialog(isShow: Boolean) {
openEditDialog = isShow
}
}
Code B
class PreferenceParameterState private constructor(context: Context) {
val (openEditDialog, set_OpenEditDialog) = mutableStateOf(false)
}
Added Content
对于 Tenfour04:谢谢!
Code C 来自官方 项目。
在 Code C 中,公共的 fun openDrawer()
和 fun resetOpenDrawerAction()
可以最终改变 val drawerShouldBeOpened
。
按照您的意见,Code D 可能是一个不错的选择,但实际上,我很少看到 Code D,而 Code C 经常出现。
如果我要优化 Code A,我应该使用 Code E 吗?
Code C
class MainViewModel : ViewModel() {
private val _drawerShouldBeOpened = MutableStateFlow(false)
val drawerShouldBeOpened = _drawerShouldBeOpened.asStateFlow()
fun openDrawer() {
_drawerShouldBeOpened.value = true
}
fun resetOpenDrawerAction() {
_drawerShouldBeOpened.value = false
}
}
Code D
class MainViewModel : ViewModel() {
var drawerShouldBeOpened = MutableStateFlow(false)
}
Code E
class PreferenceParameterState private constructor(context: Context) {
var openEditDialog by mutableStateOf(false)
}
Added Content Again:
对于 Tenfour04:谢谢!
Code F 来自另一个 项目。
而 Code F 与 Flow
不兼容,按照您的意见,Code F 应该优化为 Code G,这正确吗?
Code F
private val _freeTimeResponse = mutableStateListOf<Int>()
val freeTimeResponse: List<Int>
get() = _freeTimeResponse
private val _superheroResponse = mutableStateOf<Superhero?>(null)
val superheroResponse: Superhero?
get() = _superheroResponse.value
private val _takeawayResponse = mutableStateOf<Long?>(null)
val takeawayResponse: Long?
get() = _takeawayResponse.value
private val _feelingAboutSelfiesResponse = mutableStateOf<Float?>(null)
val feelingAboutSelfiesResponse: Float?
get() = _feelingAboutSelfiesResponse.value
private val _selfieUri = mutableStateOf<Uri?>(null)
val selfieUri
get() = _selfieUri.value
fun onSuperheroResponse(superhero: Superhero) {
_superheroResponse.value = superhero
_isNextEnabled.value = getIsNextEnabled()
}
Code G
var freeTimeResponse by mutableStateListOf<Int>()
var superheroResponse by mutableStateOf<Superhero?>(null)
var takeawayResponse by mutableStateOf<Long?>(null)
...
<details>
<summary>英文:</summary>
At present, I use Code A in my project, I think it's too complex.
But Code B get the following error
*Destructuring declarations are only allowed for local variables/values*
How can I optimize Code A ?
**Code A**
class PreferenceParameterState private constructor(context: Context) {
var openEditDialog by mutableStateOf(false)
private set
fun set_OpenEditDialog(isShow: Boolean) {
openEditDialog = isShow
}
}
**Code B**
class PreferenceParameterState private constructor(context: Context) {
val (openEditDialog, set_OpenEditDialog) = mutableStateOf(false)
}
**Added Content**
To Tenfour04: Thanks!
The Code C comes from the official [project][1].
In Code C, the public `function fun openDrawer()` and `fun resetOpenDrawerAction()` can change `val drawerShouldBeOpened` finally.
By your opinion, Code D would be good way, but in fact , I seldom see Code D, and Code C is often.
If I optimize Code A, shoud I use Code E ?
**Code C**
class MainViewModel : ViewModel() {
private val _drawerShouldBeOpened = MutableStateFlow(false)
val drawerShouldBeOpened = _drawerShouldBeOpened.asStateFlow()
fun openDrawer() {
_drawerShouldBeOpened.value = true
}
fun resetOpenDrawerAction() {
_drawerShouldBeOpened.value = false
}
}
**Code D**
class MainViewModel : ViewModel() {
var drawerShouldBeOpened = MutableStateFlow(false)
}
**Code E**
class PreferenceParameterState private constructor(context: Context) {
var openEditDialog by mutableStateOf(false)
}
**Added Content Again:**
To Tenfour04: Thanks!
The Code F comes from another [project][2]
And Code F is not working with a `Flow`, by your opinion, Code F should be optimized as Code G, is it correct?
**Code F**
private val _freeTimeResponse = mutableStateListOf<Int>()
val freeTimeResponse: List<Int>
get() = _freeTimeResponse
private val _superheroResponse = mutableStateOf<Superhero?>(null)
val superheroResponse: Superhero?
get() = _superheroResponse.value
private val _takeawayResponse = mutableStateOf<Long?>(null)
val takeawayResponse: Long?
get() = _takeawayResponse.value
private val _feelingAboutSelfiesResponse = mutableStateOf<Float?>(null)
val feelingAboutSelfiesResponse: Float?
get() = _feelingAboutSelfiesResponse.value
private val _selfieUri = mutableStateOf<Uri?>(null)
val selfieUri
get() = _selfieUri.value
fun onSuperheroResponse(superhero: Superhero) {
_superheroResponse.value = superhero
_isNextEnabled.value = getIsNextEnabled()
}
**Code G**
var freeTimeResponse by mutableStateListOf<Int>()
var superheroResponse by mutableStateOf<Superhero?>(null)
var takeawayResponse by mutableStateOf<Long?>(null)
...
[1]: https://github.com/android/compose-samples/blob/main/Jetchat/app/src/main/java/com/example/compose/jetchat/MainViewModel.kt
[2]: https://github.com/android/compose-samples/blob/main/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/survey/SurveyViewModel.kt
</details>
# 答案1
**得分**: 1
让我开始分享一下解构的工作原理。
当类具有与您想要解构的组件数量相匹配的 `fun componentN()` 时,可以对数据进行解构。
**数据类解构**
与 `toString()`、`equals()` 和 `hashCode()` 一样,`data class` 在 Kotlin 中默认支持此功能。
例如,
```kotlin
data class DataClassDestructuring(
val a: Int,
val b: Int,
)
可以这样解构:
fun test() {
val (dataClassA, dataClassB) = DataClassDestructuring(1, 2)
}
自定义类解构
我们还可以使用 operator fun componentN()
方法对其他类进行解构。
class CustomDestructureClass(
private val x: Int = 0,
private val y: Int = 0,
) {
operator fun component1(): Int {
return x
}
operator fun component2(): Int {
return y
}
}
这可以这样解构:
fun test() {
val (customClassX, customClassY) = CustomDestructureClass(1, 2)
}
MutableState 解构
由于 MutableState
定义如下,它也支持解构。
interface MutableState<T> : State<T> {
override var value: T
operator fun component1(): T
operator fun component2(): (T) -> Unit
}
fun test() {
val (state, setState) = mutableStateOf("")
}
MutableStateFlow 解构
但是,MutableStateFlow
不能被解构。如果您查看其代码,您将找不到所需的组件操作符方法。
既然我们已经了解了解构是什么以及它是如何工作的,让我们看看在进行解构时会发生什么。
从Kotlin文档中可以看到,
此语法称为解构声明。解构声明可以一次创建多个变量。
这说明解构可以一次创建多个变量。那么,为什么在函数中进行解构可以工作,但在类中却会出错呢?
fun test() {
val (dataClassA, dataClassB) = DataClassDestructuring(1, 2) // 可行
}
class DestructuringDemo {
val (dataClassA, dataClassB) = DataClassDestructuring(1, 2) // 错误
}
我们可以从错误消息中理解这一点,
解构声明仅允许用于局部变量/值。
查看Kotlin属性文档,我们可以观察到类定义了属性而不是直接的变量(或在这种上下文中的字段)。
由于属性支持 getter 和 setter,这在解构中不起作用。
问题的最后一个方面是如何在类中像ViewModel一样使用 MutableState
。
对于这个问题没有正确的答案,这取决于您的需求。
代码A - 公共getter和私有setter
var openEditDialog by mutableStateOf(false)
private set
代码E - 公共getter和公共setter
var openEditDialog by mutableStateOf(false)
修改代码E,将 val
更改为 var
- 公共getter和不可变(因此没有setter)
val openEditDialog by mutableStateOf(false)
代码F - 使用Kotlin Backing property
private val _superheroResponse = mutableStateOf<Superhero?>(null)
val superheroResponse: Superhero?
get() = _superheroResponse.value
其他选项 - 您可以像这样在属性上声明getter和setter。
如何选择?
在添加更多信息之前,我也想补充一点,Google Devs已经提到在ViewModel中添加可变状态是可以的,但是关于是否这是一个好方法存在不同的意见,因为它来自于Compose Runtime包。
我个人更喜欢在ViewModel中使用Flow而不是Compose State。这完全是个人意见。
如果您决定在ViewModel中使用Compose状态,这将是一个不错的方法。
private val _superheroResponse = mutableStateOf<Int?>(null)
val superheroResponse: State<Int?>
get() = _superheroResponse
公共的 superheroResponse
应该明确指定类型为 State
而不是 MutableState
。然后,您可以具有自定义的getter和setter,在检索或更新值之前或之后执行任何验证或业务操作。
问题中的代码E使用.value
返回值,但我们在这里返回了状态,因此可以在可组合中观察到这一点。
英文:
Let me start by sharing how destructuring works.
You can destructure data when the class has fun componentN()
for the number of components you want to destructure it to.
Data class destructuring
A data class
has this support out of the box by Kotlin similar to toString()
, equals()
, and hashCode()
.
Example,
data class DataClassDestructuring(
val a: Int,
val b: Int,
)
Can be destructured like this,
fun test() {
val (dataClassA, dataClassB) = DataClassDestructuring(1, 2)
}
Custom class destructuring
We can also have destructuring for other classes using operator fun componentN()
methods.
class CustomDestructureClass(
private val x: Int = 0,
private val y: Int = 0,
) {
operator fun component1(): Int {
return x
}
operator fun component2(): Int {
return y
}
}
This can be destructured like this,
fun test() {
val (customClassX, customClassY) = CustomDestructureClass(1, 2)
}
MutableState destructuring
As MutableState
is defined like this, it also supports destructuring.
interface MutableState<T> : State<T> {
override var value: T
operator fun component1(): T
operator fun component2(): (T) -> Unit
}
fun test() {
val (state, setState) = mutableStateOf("")
}
MutableStateFlow destructuring
But, MutableStateFlow
can not be destructured. If you look into its code you will not find the required component operator methods.
Now that we went through what destructuring is and how it works. Let's see what happens when we do it.
From the Kotlin Docs,
> This syntax is called a destructuring declaration. A destructuring declaration creates multiple variables at once.
This states that destructuring creates multiple variables at once.
So, why does it work when destructuring in a function like, but the same gives an error in a class?
fun test() {
val (dataClassA, dataClassB) = DataClassDestructuring(1, 2) // Works
}
class DestructuringDemo {
val (dataClassA, dataClassB) = DataClassDestructuring(1, 2) // Error
}
We can understand this from the error message,
> Destructuring declarations are only allowed for local variables/values.
Looking into Kotlin Docs for Properties, we can observe that the class defines properties and not variables (or fields in this context) directly.
As properties support getter and setter, this does not work for destructuring.
The last aspect of the question was how to use MutableState
in a class like ViewModel.
There is no correct answer for this, it depends on what you want.
Code A - Public getter and private setter
var openEditDialog by mutableStateOf(false)
private set
Code E - Public getter and public setter
var openEditDialog by mutableStateOf(false)
Modified Code E changing val
to var
- Public getter and immutable (so no setter)
val openEditDialog by mutableStateOf(false)
Code F - Using Kotlin Backing property
private val _superheroResponse = mutableStateOf<Superhero?>(null)
val superheroResponse: Superhero?
get() = _superheroResponse.value
Other options - You can have getters and setter declared along with the property like this
How to choose?
Before adding further info, I would like to add this as well Google Devs have mentioned that is fine to add mutable state in ViewModel, but there is mixed opinion on whether it is a good approach as it is from Compose Runtime package.
I personally prefer using Flow in ViewModel over Compose State. This is completely a personal opinion.
If you are proceeding with having Compose state in ViewModel, this would be a good approach.
private val _superheroResponse = mutableStateOf<Int?>(null)
val superheroResponse: State<Int?>
get() = _superheroResponse
The public superheroResponse
should have the type mentioned explicitly as State
and not MutableState
. Then you can have a custom getter and setter to perform any validation or business operations before or after retrieving or updating the value.
The code E in the question is returning the value using .value
, but we are returning the state here and hence this can be observed in the Composable.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论