In which situation val/var is necessary in Kotlin constructor parameter?

AndroidKotlin

Android Problem Overview


Right code:

class MainActHandler(val weakActivity: WeakReference<Activity>): Handler() {
    override fun handleMessage(msg: Message?) {
        val trueAct = weakActivity.get() ?: return
        if (msg?.what == ConversationMgr.MSG_WHAT_NEW_SENTENCE){
            val sentence = msg.obj as String?
            trueAct.conversation.text = sentence
        }
        super.handleMessage(msg)
    }
}

cannot be resolved code:

class MainActHandler(weakActivity: WeakReference<Activity>): Handler() {
    override fun handleMessage(msg: Message?) {
        val trueAct = weakActivity.get() ?: return
        if (msg?.what == ConversationMgr.MSG_WHAT_NEW_SENTENCE){
            val sentence = msg.obj as String?
            trueAct.conversation.text = sentence
        }
        super.handleMessage(msg)
    }
}

cannot be resolved code screenshot

The only difference is the "val" has been deleted and cannot be resolve.

Which might be important is that it's a inner class.

BUT

This one class without "val/var" in constructor parameter is working:

class BookInfo(convrMgr: ConversationMgr, id: String, queue: RequestQueue, queueTag:String) {

val TAG = "BookInfo"
var title: String? = ""

init {
    val url = "https://api.douban.com/v2/book/$id"
    // Request a string response from the provided URL.
    val stringRequest = StringRequest(Request.Method.GET, url,
            Response.Listener<String> { response ->
                Log.d(TAG + " Response", response.substring(0))
                // Parse JSON from String value
                val parser = Parser()
                val jsonObj: JsonObject =
                        parser.parse(StringBuilder(response.substring(0))) as JsonObject
                // Initial book title of book properties.
                title = jsonObj.string("title")
                Log.d(TAG + " Book title", title)
                convrMgr.addNewMsg(title)
            },
            Response.ErrorListener { error -> Log.e(TAG + " Error", error.toString()) })
    // Set the tag on the request.
    stringRequest.tag = queueTag
    // Add the request to the RequestQueue.
    queue.add(stringRequest)
}

}

And if I add var/val before "queue: RequestQueue", I'll get suggestion:

"Constructor parameter is never used as a property less. This inspection reports primary constructor parameters that can have 'val' or 'var' removed. Unnecessary usage of 'val' and 'var' in primary constructor consumes unnecessary memory."

I am just confused about it.

Android Solutions


Solution 1 - Android

When you write val/var within the constructor, it declares a property inside the class. When you do not write it, it is simply a parameter passed to the primary constructor, where you can access the parameters within the init block or use it to initialize other properties. For example,

class User(val id: Long, email: String) {
    val hasEmail = email.isNotBlank()    //email can be accessed here
    init {
        //email can be accessed here
    }
    
    fun getEmail(){
        //email can't be accessed here
    }
}

> Constructor parameter is never used as a property

This suggestion is saying that you do not use this property in place apart from the initialization. So, it suggests you to remove this property from the class.

Solution 2 - Android

Constructor parameters must use var or val when they are used as a property elsewhere in the class. They do not need to be properties if they are only used for class initialization.

In the example below, the parameter must be a property (var or val) because it is used in a method:

class A(val number: Int) {
    fun foo() = number
}

In this other example, the parameter is only used to initialize the class, so it does not need to be a property:

class B(number: Int): A(number) {
    init {
        System.out.println("number: $number")
    }
}

Solution 3 - Android

This might be a late answer but the magic lies under the hood:

Based on @BakaWaii's answer:

Putting var/val will make the variable a property of the class and not putting it will make it a parameter of only the constructor function.

So what does it mean, to understand lets look into some code:

class Test(a: Int){}

Now Lets see the decompiled java code:

public final class Test {
   public Test(int a) {
   }
}

So now if I try to access a using the object of Test() like the below code:

Test t = new Test(10);
t.a //Error 

It will give me error. Unresolved reference: a. Why because a is a parameter of the constructor only.

Now if we put var/val in the paramater like below:

class Test(var a: Int){}

The decompliked Java code will become:

public final class Test {
   private int a;

   public final int getA() {
      return this.a;
   }

   public final void setA(int var1) {
      this.a = var1;
   }

   public Test(int a) {
      this.a = a;
   }
}

Thus it will not only give you a class property but also give you getter/setters for setting the values.

Now the next question arises if the field a is private how can it be accessed. Simple answer in Java you cannot, i.e. if you are calling the KT class from a Java you will not be able to assign value of a like Test(1).a = 10 but will have to use Test(1).setA(5). But as kotlin internally handles getters/setters Test(1).a = 5 will be ok.

Solution 4 - Android

For @Parcelize to work you need to open up the super's properties and override them in the child:

abstract class Goal(open var number: Int, open var name: String) : Parcelable

@Parcelize
class OperationalGoal(override var number: Int, override var name: String, var description: String) : Goal(number, name)```

Solution 5 - Android

In very simple terms, use var or val in class constructor parameters when you want to use that variable, say, inside a method within that class.

class User(var name: String, age: Int) {
    var str = "John"
    var num = 18

    //name = str //this will result in a compile error if you uncomment it

    fun setName(){
        name = str //due to using var on class constructor parameter, we access constructor variable *name* inside a method
    }

    fun setAge(){
        //age = num //this will result in a compile error if you uncomment it, because var wasn't used in the *age* parameter within the class constructor, which means we can't access *age*
    }
    
}

Run this Kotlin Playground code to get a clearer idea of what's going on.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionKorey LereView Question on Stackoverflow
Solution 1 - AndroidBakaWaiiView Answer on Stackoverflow
Solution 2 - AndroidBryan HerbstView Answer on Stackoverflow
Solution 3 - AndroidSayok MajumderView Answer on Stackoverflow
Solution 4 - AndroidAbhilash DasView Answer on Stackoverflow
Solution 5 - AndroidM.EdView Answer on Stackoverflow