Expressive Kotlin. Extensions

  • Tutorial
No one likes repeatable code. Nevertheless, there are constructions that have taken root and are rooted in programming for a long time, despite this very repeatability.
There is such a commonly used data binding construct in android:

fun bindCell1(view: View, data: Data) {
    view.cell1_text.setText(data.titleId)
    view.cell1_icon.setImageResource(data.icon)
}

The obvious method, which has one very annoying sloppiness for me, is to specify view links each time. and data. Each line contains 10 characters, which are obvious.

And Kotlin has a way around this sloppiness - Extensions. You can read more about them here .

Brief annotation to the article
In this article I am not going to inculcate in someone a certain programming style. I just want to show the language features of Kotlin as an example of a common task. You can solve this problem as you like, without even using Kotlin. Please refrain from holivars in the comments - this is a purely technical article.

We implement the method as an extension for the data class.
Convert our design to

fun Data.bindCell1(view: View) {
    view.cell1_icon.setImageResource(icon)
    view.cell2_text.setText(titleId)
}

How is that? The bindSome method is now not in itself, but an extension to the data class. It turns out that this method behaves like a method of the Data class itself. There is one limitation - protected and private entities are not visible in extensions - which is logical, since in reality the extension is not registered in the class itself. However, combining internal and public properties, you can get quite safe combinations. Accordingly, you can now directly access the properties of the Data instance itself.

Now try to get rid of the view prefix . . to do this, create an immutable property

val Data.bindMethod_cell_2: View.() -> Unit
    get() = {
        cell2_icon.setImageResource(icon)
        cell2_text.setText(titleId)
    }

How so?


Now the bindMethod property is an extension for the MediaData class, and at the same time, by type of data, an extension for the View!

So what's next?


And then we can call this construct as a normal method, while passing View as an argument!

data.bindMethod(view)

And if we go even further, we can pass View. () -> Unit as an argument.

What does this give us?


For example, we can not typify the RecyclerView object from the word at all, passing only the layout ID and the resulting binding function into it. At the very beginning, the bindSome (view: View, data: Data) function was strongly typed, but now we do not depend on this data type at all. - the data type (View. () -> Unit) is bound only to View.

And the intersection of namespaces?


It happens when the property names inside the View and Data match. Purely technically, all this just gets around (you can add a prefix to the name of the layouts), but you can also follow a simple path:

val Data.bindMethod_cell_1: View.() -> Unit
    get() = {
        this.cell1_icon.setImageResource(this@bindMethod_cell_1.icon)
        this.cell1_text.setText(this@bindMethod_cell_1.titleId)
    }

Is that the design came out longer.

But what about the arguments?


If bindMethod has arguments, when calling this method, the View object will be passed as the first argument, after which the remaining arguments will be passed, as we usually call.

val Data.bindMethod: (View.(Int, String)->Unit) get() = { intValue, str ->
    view.numText.text = str.replace("%s", intValue.toString())
}
//--------------------------------------
data.bindMethod.invoke(view, 0, "str%s")


This method will allow us to collect all the binding methods in one place, and do, for example, like this:

Separate documents example
class Data( val name:String, val icon:String)
//-----------------------------------
// DataExtensions.kt
fun Data.carAdapter() = PairUnit>(
	R.layout.layout_car_cell, {
        carcell_title.text = name
        carcell_icon.setImage(icon)
    })
fun Data.motoAdapter() = PairUnit>(
	R.layout.layout_moto_cell, {
        moto_icon.setImage(icon)
    })

Note that carAdapter and motoAdapter do not lie inside the Data class. they can be located anywhere at all - you want, take it to the extension, you want, leave it with the class. You can call them from anywhere, extensions are imported like classes.

The materials used in the article, I compiled into a small project

Also popular now: