Kotlin Data class copy extension - kotlin

I am trying to find a solution for a nice kotlin data class solution. I have already this:
data class Object(
var classMember: Boolean,
var otherClassMember: Boolean,
var example: Int = 0) {
fun set(block: Object.() -> kotlin.Unit): Object {
val copiedObject = this.copy()
copiedObject.apply {
block()
}
return copiedObject
}
fun touch(block: Object.() -> kotlin.Unit): Object {
return this.set {
classMember = true
otherClassMember = false
block() }
}
}
val test = Object(true,true,1)
val changedTest = test.touch { example = 2 }
the result of this method is that the changedTest object has classMember = true, otherClassMember = false and example = 2
The problem with this solution is, the class properties are not immutable with var declaration. Does somebody have an idea how to optimize my methods to change var to val?

val says that a variable can't change it's value after initialization at the definition point. Kotlin's generated copy method does not modify an existing copy after construction: this method actually uses retrieved values from an object, replaces these values with ones that provided in copy method (if any), and after that just constructs a new object using these values.
So, it is not possible to perform such an optimization if you are going to change object's state after construction.

If I understood what you want correctly, you can do
data class Object(
val classMember: Boolean,
val otherClassMember: Boolean,
val example: Int = 0) {
fun touch(example: Int = this.example): Object {
return copy(
classMember = true,
otherClassMember = false,
example = example)
}
}
val test = Object(true,true,1)
val changedTest = test.touch(example = 2)
Though you need to repeat parameters other than classMember and otherClassMember but without reflection you can't do better.

Related

Why can the author reassign a new object to a val via update?

The Code A is from offical sample code here.
The private val _uiState is val, in my mind, a val can be only assigned a object for one time.
It seems that _uiState.update { it.copy(loading = true) } shows _uiState is assigned to a new object again by update.
I don't understand why the author can reassign a new object to a val via update, could you tell me?
Code A
data class InterestsUiState(
val topics: List<InterestSection> = emptyList(),
val people: List<String> = emptyList(),
val publications: List<String> = emptyList(),
val loading: Boolean = false,
)
class InterestsViewModel(
private val interestsRepository: InterestsRepository
) : ViewModel() {
// UI state exposed to the UI
private val _uiState = MutableStateFlow(InterestsUiState(loading = true))
val uiState: StateFlow<InterestsUiState> = _uiState.asStateFlow()
private fun refreshAll() {
_uiState.update { it.copy(loading = true) }
...
}
...
}
data class InterestsUiState(
val topics: List<InterestSection> = emptyList(),
val people: List<String> = emptyList(),
val publications: List<String> = emptyList(),
val loading: Boolean = false,
)
/**
* Updates the [MutableStateFlow.value] atomically using the specified [function] of its value.
*
* [function] may be evaluated multiple times, if [value] is being concurrently updated.
*/
public inline fun <T> MutableStateFlow<T>.update(function: (T) -> T) {
while (true) {
val prevValue = value
val nextValue = function(prevValue)
if (compareAndSet(prevValue, nextValue)) {
return
}
}
}
Added Content
To Chaoz: Thanks!
But all members in data class InterestsUiState(...val loading: Boolean = falseļ¼‰is val type, and you can't change any member vaule when you have created the object of InterestsUiState.
So I can't still understand why the member value of _uiState can be changed when the author launch _uiState.update { it.copy(loading = true) }.
And more
_uiState.update { it.copy(loading = true) } is equal to
_uiState.value = _uiState.value.copy(loading = true), right?
The val keyword only refers to which object the variable holds, not the data inside said object. For example:
class MyClass(var value: Int)
The following code is not allowed:
val obj = MyClass(5)
obj = MyClass(7) // compile error
because the val keyword refers to the variable itself being reassigned to a different object. This code, however, is allowed:
val obj = MyClass(5)
obj.value = 7
Here, obj is still the same object, only a property of said object changed value. In your provided code, the update function modifies data stored inside the _uiState object, however it does not swap it for a new object. This is important because:
var obj = MyClass(5)
val copy = obj
obj = MyClass(7)
println(copy.value) // prints 5
println(obj.value) // prints 7
When reassigning a variable, the old object remains, and any other variables referencing that object are not updated. In your case, _uiState.value is modified, not the variable itself. Hope this clears things up!
Edit:
Yes, it.copy() is an expression which creates a new object. However, this code is executed in the line _uiState.update { it.copy(loading = true) }, in the refreshAll() function. As it is the last statement in a lambda expression (also the only one, but doesn't matter), it is the return value of said lambda. Here we have to look at the declaration of the update function.
The lambda is stored in the function variable (of type (T)->T). This means, whenever function() is called, the code inside the lambda is executed, and its result (a new object) is then returned by the function() call. This value is assigned to the val nextValue variable and not to _uiState itself. The compareAndSet function modifies _uiState.value and does not change the object the _uiState variable references.
And by the way, the object returned by it.copy() is of type T, and not of type MutableStateFlow<T>, which your _uiState variable holds. T is the type of _uiState.value.

Can I set an object variable with a reflection value to a property of another object?

I have a class that has a property whose type is KMutableVar1. The objects of that class have a variable assigned to a reflection of another class's property. I have a function that is supposed take in an object of the first class and uses its variable of type KMutableVar1 to determine which property of an object of the second class to edit.
jeez that paragraph is awful im so so sorry ><
I have already tried assigning the object's KMutableVar1 variable to another variable and then trying to tie that variable to an object using dot notation, but that variable name isn't in the primary constructor for the class and thus an error occurs.
class Thing(var amount: Int, var id: Int){
fun editAttributes(object: Thing, editor: RemoteEdit){
//My initial thought here was to do the following:
var editing = editor.attributeToEdit
object.editing = editor.newValue
//But this raises an error since class 'thing' has no attribute 'editing'
}
}
var bananas = Thing(amount = 12, id = 21)
class RemoteEdit(var attributeToEdit: KMutableVar1, var newValue: Int)
var remoteEditor = RemoteEdit(attributeToEdit = Thing::amount, newValue = 23)
My intent is for the function to change bananas.amount to 23.
Sorry, I'm not fully understanding why you need it but it will work I guess:
import kotlin.reflect.KMutableProperty1
class Thing(var amount: Int, var id: Int) {
fun editAttributes(editor: RemoteEdit) {
val editing = editor.attributeToEdit
editing.set(this, editor.newValue)
}
}
class RemoteEdit(var attributeToEdit: KMutableProperty1<Thing, Int>, var newValue: Int)
fun main() {
val bananas = Thing(amount = 12, id = 21)
val remoteEditor = RemoteEdit(attributeToEdit = Thing::amount, newValue = 23)
bananas.editAttributes(remoteEditor)
println(bananas.amount) // prints 23
}
I have a feeling this might be an XY problem because there are so many unusual things going on in your code. Why would the implementation of changing the property value through reflection be in the class that's being edited?
I suppose if there is some reason you need to be able to pass these parameters for editing around, you would need a class, but then it makes sense for it to implement the function for using it by itself:
class RemoteEdit<T, R>(var attributeToEdit: KMutableProperty1<T, R>, var newValue: R) {
fun execute(item: T) {
attributeToEdit.set(item, newValue)
}
}
val bananas = Thing(amount = 12, id = 21)
val edit23 = RemoteEdit(Thing::amount, 23)
edit23.execute(bananas)
If you don't need to pass these around, all you need is a top level function:
fun <T, R> editProperty(item: T, attributeToEdit: KMutableProperty1<T, R>, newValue: R) =
attributeToEdit.set(item, newValue)
val bananas = Thing(amount = 12, id = 21)
editProperty(bananas, Thing::amount, 23)

Kotlin: How to specify a named arguent with a variable?

Suppose I have two methods:
private fun method1(a: A): A {
return a.copy(v1 = null)
}
private fun method2(a: A): A {
return a.copy(v2 = null)
}
Can I write something like:
private fun commonMethod(a: A, variableToChange: String): A {
return a.copy($variableToChange = null)
}
Another words, can I use a variable to refer to a named argument?
If I understand correctly what you are trying to archive I would recommend to pass a setter to the method e.g.
fun <A> changer (a: A, setter: (a: A) -> Unit ) {
// do stuff
setter(a)
}
Is this what you are looking for?
A possible solution for this problem (with usage of reflection) is:
inline fun <reified T : Any> copyValues(a: T, values: Map<String, Any?>): T {
val function = a::class.functions.first { it.name == "copy" }
val parameters = function.parameters
return function.callBy(
values.map { (parameterName, value) ->
parameters.first { it.name == parameterName } to value
}.toMap() + (parameters.first() to a)
) as T
}
This works with all data classes and all classes that have a custom copy function with the same semantics (as long as the parameter names are not erased while compiling). In the first step the function reference of the copy method is searched (KFunction<*>). This object has two importent properties. The parameters property and the callBy function.
With the callBy function you can execute all function references with a map for the parameters. This map must contain a reference to the receiver object.
The parameters propery contains a collection of KProperty. They are needed as keys for the callBy map. The name can be used to find the right KProperty. If a function as a parameter that is not given in the map it uses the default value if available or throws an exception.
Be aware that this solution requires the full reflection library and therefore only works with Kotlin-JVM. It also ignores typechecking for the parameters and can easily lead to runtime exceptions.
You can use it like:
data class Person (
val name: String,
val age: Int,
val foo: Boolean
)
fun main() {
var p = Person("Bob", 18, false)
println(p)
p = copyValues(p, mapOf(
"name" to "Max",
"age" to 35,
"foo" to true
))
println(p)
}
// Person(name=Name, age=15, foo=false)
// Person(name=Max, age=35, foo=true)

Overwrite self with extension property setter

I want a Kotlin property that can replace the original value. For example:
var String.setString
get() = "ignorethis"
set(value) = this = value
(I'm actually using it on UBytes to add getters and setters for the upper and lower nibbles).
However, in my example above I get Variable expected. Is it possible to modify this with an extension property?
String is immutable, and you can only do that on mutable types like this:
fun main() {
val x = mutableListOf("old")
x.setString = mutableListOf("New Pro Max Extra Large Plus")
println(x)
}
var <T> MutableList<T>.setString
get() = this
set(value) = value.forEachIndexed { idx, it -> this[idx] = it } ```

Kotlin - Destructuring in loops not working - var not accessible

some background:
val (name, age) = person
This syntax is called a destructuring declaration. It creates multiple variables (correction, creates multiple values) at at the same time.
Destructuring declarations also work in for-loops: when you say:
for ((a, b) in collection) { ... }
Lets take a look at a list item i have:
#Parcelize
data class MyModel(
var name: String = "",
var is_locked: Boolean = true,
var is_one_size: Boolean = false,
) : Parcelable
and now i have obtained a list of "MyModel" classes and i am trying to loop over them like this:
private fun initMyModelList(model: MutableList<MyModel>) {
//i want to access is_locked from here with destruction but i cant ? IDE telling me the type is an int but its clearly defined as a Boolean
for((is_locked) in model){
//what i want to do in here is access the is_locked var of the model list and change all of them in a loop. im trying to use Destructuring in loop as a conveience. why is it not working ?
//how can i make the call signature look like this--- > is_locked = true instad of model.is_locked =true
}
}
all i want to do is be able to call is_locked = true instead of model.is_locked = true within the loop. how can this be done ?
This syntax is called a destructuring declaration. It creates multiple variables at at the same time.
It doesn't create multiple variables, it captures multiple values. You're working with values, not references, as your source tells further:
A destructuring declaration is compiled down to the following code:
val name = person.component1()
val age = person.component2()
Closest to what you want would be this custom extension function:
inline fun <E> Iterable<E>.withEach(block: E.() -> Unit) {
forEach {
it.block()
}
}
Use like so:
model.withEach {
is_locked = true
}
Before you ask the obligatory question "why isn't this included in stdlib?" consider that functional style programming typically is about transforming immutable types. Basically, what I did here was encourage a bad habit.
Basically, it isn't possible, cause your code is compiled to something like:
for (m in models) {
val is_locked = m.component1()
...
}
Which means that you create a local property which cannot be reassigned. But you can do something like this:
for (m in model) {
with(m) {
is_locked = true
}
}
Yep, it isn't perfect, but it can be improved with extension methods:
fun <T> List<T>.forEachApply(block: T.() -> Unit) {
forEach(block)
}
private fun initMyModelList(model: MutableList<MyModel>) {
model.forEachApply {
is_locked = true
}
}
You can use destructuring in a loop just fine as read-only values.
data class Stuff(val name: String, val other: String)
fun doStuff() {
val stuff = Stuff("happy", "day")
val stuffs = listOf(stuff)
for ((name) in stuffs) {
println(name)
}
}
Running that method prints "happy" to the console. Baeldung shows an example of using it here.
It's best practice for data classes to be immutable, so I would try to rewrite your data class to be immutable. The .copy function will let you copy your data class but with new, different values.