3

I am new to kotlin. I wonder if this is possible

I wish to create a function that will change the value of the properties of the object and return the object itself. The main benefit is that I can chain this setter.

class Person {
   var name:String? = null
   var age:Int? = null

   fun setter(propName:String, value:Any): Person{
      return this.apply {
          try {
              // the line below caused error
              this[propName] = value
          } catch(e:Exception){
              println(e.printStackTrace()) 
          }
      }
   }
}


//usage
var person = Person(null,null)
person
   .setter(name, "Baby")
   .setter(age, 20)

But I get error "unknown references"

This question is marked as duplicate, however the possible duplicate question specifically want to change the property of "name", but I wish to change anyProperty that is pass from the function to object. Can't seem to connect the dot between two questions. @Moira Kindly provide answer that explain it. thankyou

4
  • Possible duplicate of How to change a member field with Kotlin reflection? Commented Aug 30, 2018 at 5:06
  • @Moira if it's duplicate, could you please answer it? I can't seem to connect the dots, since I am new to java and kotlin Commented Aug 30, 2018 at 5:54
  • You can use the "apply" in kotlin to achieve this. I am no kotlin expert, but I read an interesting article about the somewhat related problem. I don't knot if it will solve your problem though. medium.com/@vicidroiddev/… Commented Aug 30, 2018 at 6:19
  • 1
    @OleReidarHolm , yeah. I kind of thinking of using apply too. But I feel very itchy and curious to find out how to do it this way in kotlin. Commented Aug 30, 2018 at 7:37

3 Answers 3

6

Why not just simplify your answer to

fun setter(propName: String, value: Any): Person {
    val property = this::class.memberProperties.find { it.name == propName }
    when (property) {
        is KMutableProperty<*> ->
            property.setter.call(this, value)
        null -> 
            // no such property
        else ->
            // immutable property
    }
}

Java reflection isn't needed, its only effect is to stop non-trivial properties from being supported.

Also, if you call it operator fun set instead of fun setter, the

this[propName] = value

syntax can be used to call it.

Sign up to request clarification or add additional context in comments.

Comments

2

After googling around, I think I can provide an answer, but relying on java instead of kotlin purely. It will be great if someone can provide a better answer in kotlin.

class Person(
    var name: String,
    val age: Int
){

  fun setter(propName: String, value: Any): Person{
    var isFieldExistAndNotFinal = false
    try{
        val field = this.javaClass.getDeclaredField(propName)
        val isFieldFinal = (field.getModifiers() and java.lang.reflect.Modifier.FINAL == java.lang.reflect.Modifier.FINAL)
        if(!isFieldFinal) {
            // not final
            isFieldExistAndNotFinal = true
        }
        // final variable cannot be changed
        else throw ( Exception("field '$propName' is constant, in ${this.toString()}"))
    } catch (e: Exception) {
        // object does not have property
        println("$e in ${this.toString()}")
    }

    if(isFieldExistAndNotFinal){
        val property = this::class.memberProperties.find { it.name == propName }
        if (property is KMutableProperty<*>) {
            property.setter.call(this, value)
        }
    }
    return this;
  }
}

usage like this

 person
    .setter(propName = "age", value = 30.00)
    .setter(propName = "asdf", value = "asdf")
    .setter(propName = "name", value = "A Vidy")

Comments

1

You have error because when you do this[propName] = value you are trying to use this as a list, but it is not a list, it is a Person and it doesn't overload the [] operator.

What you can do is to add a check for the property that is setted:

class Person {
   privavar name:String? = null
   var age:Int? = null  

   fun setter(propName:String, value:Any): Person{
      return this.apply {
          if (propName == "name" && value is String?) {
              it.name = value as String?
          } else if (propName == "age" && value is Int?) {
              it.age = value as Int?
          } else {
              // handle unknown property or value has incorrect type
          }
      }
   }
}

Another more dynamic solution without reflection:

class Person {
   private var fields: Map<String, Any?> = HashMap()

   fun setter(propName:String, value:Any): Person{
      return this.apply {
          it.fields[propName] = value;
      }
   }

   fun getName() = fields["name"]
}

If you want to get rid of the getters as well then you need to use reflection.

3 Comments

not very dynamic right isn't it, with a lot of if cases
I mean, if I add more properties, then I need to add if condition plus a lot of type checking, similar effort when deleting properties. This code can be too troublesome to read and manage after a month of two.
Yes, that's true. If you want it to be more dynamic you have to find the fields by using reflection. Or remove all the fields of the class Person and add a single field which is Map<String, Any>. But you will then need to define the getters.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.