Call a default constructor from another one in Kotlin
Asked Answered
S

1

17

In Kotlin, when a class has multiple constructors how can we call the designated (coming from iOS world I could not find a better name) constructor from within another constructor.

Let me show you an example

final class LoadingButton: LinearLayout {
    var text:String? = null

    constructor(context: Context, text: String) : this(context) {
        this.text = text
    }
    constructor(context: Context) : super(context) {
        val t = this.text
        this.view {
            button(t) { /* ... */}
        }
    }
}

Here if I do loadingButton("TEST", {}) that string is not propagated to the button because this(context) is called before the code inside the convenience constructor (sorry again :).

Can that be solved in Kotlin ? Something like

constructor(context: Context, text: String) {
    this.text = text
    this(context) 
}

EDIT

Just to clarify the idea since it's been asked, the idea is to write something like this in an activity :

onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    verticalLayout {
        loadingButton("Some text")
        //or
        loadingButton() { this.text = "Some text"}
}

This is obviously not that much useful but you get the idea. The text property can be known at construction or later on.

This is especially not useful since Kotlin has default values for parameters but I am studying the language and ran into this problem.

EDIT 2

Another clarification is that I'm using Anko for the layout, so the loadingButton methods look like this:

inline fun ViewManager.loadingButton() = loadingButton {  }
inline fun ViewManager.loadingButton(init: LoadingButton.() -> Unit) = ankoView({ LoadingButton(it) }, init)
inline fun ViewManager.loadingButton(text: String, init: LoadingButton.() -> Unit) = ankoView({ LoadingButton(it, text) }, init)
Subdebutante answered 14/3, 2016 at 17:32 Comment(5)
Wouldn't it make more sense to call the first constructor from the second one, and pass null as argument for the text? That's how you would do it in Java, too.Assoil
Which constructor does LoadingButton("TEST") call? I mean, they both have incompatible signatures..Sulphathiazole
@Subdebutante please clarify your intend of LoadingButton("TEST") Sulphathiazole
Even after the update I still do not get the situation, so here is a specifying question - you use LoadingButton("TEST"), but I do not see a constructor with a signature like LoadingButton(String). Is this a typo? Assuming Context is not a StringSulphathiazole
You're right @Sulphathiazole I've added the requested code in another editSubdebutante
S
29

The code with a post-call to a constructor cannot exist on JVM since you MUST call super(...) before you do anything with the class itself. Think of it as if the super class contained a private field, you would have to initialise it before you can use it.

This is usually not a problem since you can call the constructors the other way around:

constructor(context: Context, text: String?) : super(context) {
    this.text = text
    this.view {
        button(text) { /* ... */}
    }
}
constructor(context: Context) : this(context, null)
constructor(context: Context, text: String) : this(context, text)

The code above does roughly the same as a default argument:

constructor(context: Context, text: String? = null) : super(context) {
    this.text = text
    this.view {
        button(text) { /* ... */}
    }
}

To make this code idiomatic (and concise), use a primary constructor:

class LoadingButton(context: Context, val text: String? = null): LinearLayout(context) {
    init {
        this.view {
            button(text) { /* ... */}
        }
    }
}

The terminology goes as following: designated - primary, convenience - secondary

See Classes and Inheritance - Kotlin Programming Language for more details.

Sulphathiazole answered 14/3, 2016 at 18:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.