vdj4y
vdj4y

Reputation: 2669

Kotlin general setter function

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

Upvotes: 3

Views: 1338

Answers (3)

tudorprodan
tudorprodan

Reputation: 965

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.

Upvotes: 1

Alexey Romanov
Alexey Romanov

Reputation: 170745

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.

Upvotes: 6

vdj4y
vdj4y

Reputation: 2669

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")

Upvotes: 2

Related Questions