Skip to content

3.) The Cool Stuff

Gabor Varadi edited this page May 13, 2019 · 31 revisions

lambda types, trailing lambdas

In Kotlin, you have a first-party support for "lambda types", which represent a method with a given set of arguments, and a given return type.

Of course, in Java, you could also attempt to mimic it with java.util.function.*, but Kotlin's syntax is much more straightforward and easy to use. This might sound like a pitch, but just look at it:

class MyAdapter(
    private var items: List<Item>,
    private val clickListener: (Item) -> Unit
): RecyclerView.Adapter<MyAdapter.ViewHolder>() {
    ....
}

Where this is created as:

val adapter = MyAdapter(items) { item ->
    doSomethingWith(item)
}

As the last "lambda" argument is passed "outside" the parentheses.

If we are passing multiple lambdas, it is preferred to use named arguments for both.

createDialog(title, description,
    onPositiveClick = { yay() },
    onNegativeClick = { nay() }
)

What is important to note is that interfaces defined in Java can be used with lambda types of Kotlin thanks to SAM conversion.

Another thing to know is that a single-argument lambda implicitly names its argument it, but any name can be given to that variable.

If we have multiple variables but there are unused ones, we can use _ as a name for them.

someView.setOnClickListener { view -> // if unnamed, it is called `it`
    // ...
}

someView.waitForMeasure { _, _, _ -> 
    // unused parameters can be named as `_`
}

Beware: Kotlin code can become very messy if it is over-used. Pretend it is called x. If it is hard to read without IDE support, don't use it.

Also important: One more thing to note is that in a lambda expression in Kotlin, the last line's return value is returned as the return value, and return is explicitly not required.

However if we still want to add a return (or we need to return from a lambda above the last line), then we can use return@[caller], for example return@onClick.

method reference

Any function of any class can be passed as a lambda using the function reference operator.

fun acceptsLambda(lambda: () -> Unit) {
    if(...) {
        lambda() // same as `lambda.invoke()`
    }
}

fun doSomething() {
}

fun execute() {
    acceptsLambda(::doSomething)
}

typealias

One can use a typealias to give a name to a given type.

What's nice is that one can also provide template parameters for typealiases.

For example, it can be used to name lambda parameters in case they are unclear.

typealias ItemClickListener = (Item) -> Unit

class MyAdapter(
    private var items: List<Item>,
    private val clickListener: ItemClickListener
): RecyclerView.Adapter<MyAdapter.ViewHolder>() {
    ....
}

lambdas with receivers

Kotlin has a very powerful construct called a "lambda with receiver". This means that inside a given lambda, the parameter it's invoked on is seen as this.

For example:

inline fun Realm.runTransaction(crossinline transaction: Realm.() -> Unit) {
    val realm = this
    realm.executeTransaction {
        transaction(realm)
    }
}

realm.runTransaction {
    insertOrUpdate(Dog("Hello"))
    insertOrUpdate(Cat("World"))
}

For more info on this, you can check out https://academy.realm.io/posts/kau-jake-wharton-testing-robots/

extension functions, extension properties

Extension functions are functions you define that lets you invoke functions on given classes as if they were functions of that given class.

This enables you with the ability to enhance classes without having to extend them or wrap them with custom classes - instead, define a function somewhere as a top-level extension function, and you can "magically" use this function anywhere (as long as you import it).

fun View.show() {
    this.visibility = View.VISIBLE
}

fun View.hide() {
    this.visibility = View.GONE
}

// myView.visibility = View.VISIBLE
myView.show()

You can also specify extension properties.

var View.isVisible: Boolean
    get() = this.visibility == View.VISIBLE
    set(value) {
        if (value) {
            show()
        } else {
            hide()
        }
    }

In Kotlin, this feature might be the most powerful feature, especially considering it respects generic bounds, too.

inline fun Array<Account>.forEachNonDemo(action: (Account) -> Unit) {
    this.filter { !it.isDemo() }.forEach(action)
}

And it allows you to add numerous "utils" or "helper" functions to your class as if it was already part of it, without using inheritance to add new behavior.

fun View.objectAnimate() = ViewPropertyObjectAnimator.animate(this)

And just to show you a real example for what this looks like, this is what I use for the fade animation of views (this is actually what I was showing off as an example in this thread on Reddit about benefits of using Kotlin.)

fun View.animateFadeOut(duration: Long = 325, hideOnFinish: Boolean = true): Animator = run {
    alpha = 1f
    objectAnimate()
        .alpha(0f)
        .setDuration(duration)
        .get()
        .apply {
            if (hideOnFinish) {
                onFinish {
                    hide()
                }
            }
        }
}

fun View.animateFadeIn(duration: Long = 325, showOnStart: Boolean = true): Animator = run {
    alpha = 0f
    objectAnimate()
        .alpha(1f)
        .setDuration(duration)
        .get()
        .apply {
            if (showOnStart) {
                onStart {
                    show()
                }
            }
        }
}

So if you have any DateUtils or StringUtils or ContextUtils class, it should probably be top-level extension functions.

standard library functions - scoping functions: apply, let, also, run, with

Now that we know we can create extension functions and we can also apply generic bounds to them, and we also know about lambdas-with-receivers, now we can understand the standard library scoping functions and how they work.

First we have let and run, which can return a new type after performing the lambda:

inline fun <R, T> T.let(transform: (T) -> R): R = transform(this) 
inline fun <R, T> T.run(transform: T.() -> R): R = transform(this)

Then we have also and apply, which return the current object once the lambda is executed:

inline fun <T> T.also(action: (T) -> Unit): T {
    action(this)
    return this
}
inline fun <T> T.apply(action: T.() -> Unit): T {
    action(this)
    return this
}

And we have one last odd-one-out called with, which works like apply except it is a top-level function instead of an extension function.

inline fun <T> with(t: T, action: T.() -> Unit) {
    action(t)
}

If you've not really seen Kotlin before, then you might be thinking "wait WTF how are these useful", but trust me they make code much simpler once you know how to use them.

Here are a few noteworthy examples.

  • apply/also

apply is often used when an object is being configured before it is assigned to a value (or returned from a function).

also is used for the same thing, except it allows you to rename the variable you are operating on, instead of seeing it as this.

For example,

fun okHttpClient() = OkHttpClient.Builder().apply {
    setInterceptor(NetworkInterceptor())
    baseUrl(BuildConfig.BASE_URL)
}.build()

Or

fragment.arguments = Bundle().also { bundle -> 
    bundle.putString("hello", "hello")
}

Imagine that you are writing Java. You need to create a local variable instead of writing the assignment directly.

Bundle bundle = new Bundle();
bundle.putString("hello", "hello");
fragment.setArguments(bundle);

With scoping functions, we can merge these operations together. This is especially nice if you're working with mutable lists that you are adding items into.

  • let/run

let lets you execute a block which can return a new type after its execution. run lets you do the same thing, except you see the current object as this.

This can be useful if you don't want to create a local variable but you're "mapping" your object into something else.

val domainObject = dao.findFirst<MyClass>().let { DomainObject(it) }

It is also fairly common to combine let with the ?. and ?: safe-call / elvis operators.

run is similar but when you want to use the current object as this. Another nice trick with run is that you can turn a multi-line function into a single-line expression, here is an example.

fun someFunction() {
    val someValue = "beep" // some local variable you cannot avoid, or scoping functions would make hard to read
    return result
}

We can turn this into:

fun someFunction() = run {
    val someValue = "beep"
    result
}
  • with

with can be used for reducing the number of times we refer to the same variable over and over again.

For example,

with(recyclerView) {
    adapter = MyAdapter()
    layoutManager = LinearLayoutManager(this@MyActivity) 
}

standard library functions - more utility functions: takeIf, takeUnless

There are two more standard library functions that people actually often forget to mention, takeIf and takeUnless (in which case takeUnless is actually just != for takeIf).

inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? {
    if(predicate(this)) { 
        return this 
    }
    return null
}

Or with a when statement (just to get used to using it):

inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? = when {
    predicate(this) -> this
    else -> null
}

It might seem like an odd function, but it's actually very powerful in certain assignments, especially if you need to define a non-null default value that you'd prefer to ignore.

int someNumber = bundle.getInt("someNumber", -1);
if(someNumber == -1) {
   // uninitialized!
}

As it is uninitialized, we might want to model it with null instead.

Integer someNumber = bundle.getInt("someNumber", -1);
if(someNumber == -1) {
   someNumber = null;
}

And we can turn this into a single-liner with Kotlin.

val someNumber = bundle.getInt("someNumber", -1).takeIf { it >= 0 }

And it gets better when we combine this with ?.let.

val someText = bundle.getInt("someNumber", -1).takeIf { it >= 0 }?.let { "$it" }

And maybe even give it a default non-null value.

val someText = bundle.getInt("someNumber", -1).takeIf { it >= 0 }?.let { "$it" } ?: ""

Or throw an exception.

val someText = something.takeIf { condition } ?: throw IllegalArgumentException("$something is invalid")

tailrec keyword

In Kotlin, a recursive function that is "tail-recursive" can be forced to be optimized, in the sense that its real implementation will use a for-loop (iterative solution) instead of a recursive one. This means the function does not need to use the stack to store previous results and is therefore safe to use even for larger numbers.

When you write a recursive function, it's best to check if you can use tailrec. If you can, then the compiler won't nag you about it.

tailrec fun <T: Activity> findActivity(context: Context): T {
    if(context is Activity) {
        return context as T
    }
    if(context is ContextWrapper) {
        val baseContext = context.baseContext
        return findActivity(baseContext)
    }
    throw IllegalArgumentException("The context does not contain an Activity reference!")
}

inline functions (crossinline, noinline)

I've previously used inline functions without explaining them; it means that the lambda we pass in will be inlined instead of creating an anonymous implementation for the given lambda type.

This pretty much means that inline lambdas don't have the cost of creating an anonymous instance for the interface, and that means it's cheaper as there is no actual object instance instantiation by calling a function with a lambda argument!

inline fun <T> doSomething(action: (T) -> Unit) {
    // action is inlined instead of creating a new 
    // anonymous implementation / instance of the lambda interface
}

However, due to some trickery in how inline returns work, there are restrictions sometimes, which once we understand can actually still be quite handy.

1.) noinline means that the argument won't be inlined even if it's an inline function

2.) crossinline means that the argument will be invoked inside another lambda that is not inlined.

Okay, you may ask "when are these useful?"

noinline is useful if you need the lambda to be an instance, for example if it is actually being passed to a SAM-converted method.

crossinline is typically useful if you're passing the lambdas as implementations to an object with multiple implementation. I'll show you my favorite example of this.

myEditText.addTextChangedListener(object: TextWatcher {
    override fun afterTextChanged(s: Editable) {
         newValue = s.toString()
    }

    override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {                
    }

    override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
    }
})

can be converted into

inline fun EditText.onTextChanged(crossinline eventHandler: (String) -> Unit) {
    this.addTextChangedListener(object: TextWatcher {
        override fun afterTextChanged(s: Editable) {
             eventHandler(s.toString())
        }

        override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {                
        }

        override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
        }
    })
}

And now you can use it:

myEditText.onTextChanged {
    newValue = it
}

So we had to add crossinline to be able to call our lambda inside the object: TextWatcher's functions. This actually happens quite often, as we can see in another example:

inline fun <reified T: ViewModel> AppCompatActivity.createViewModel(
    crossinline factory: () -> T
): T = T::class.java.let { clazz ->
    ViewModelProviders.of(this, object: ViewModelProvider.Factory {
        override fun <T : ViewModel?> create(modelClass: Class<T>): T {
            if(modelClass == clazz) {
                @Suppress("UNCHECKED_CAST")
                return factory() as T
            }
            throw IllegalArgumentException("Unexpected argument: $modelClass")
        }
    }).get(clazz)
}

In which case we invoke the lambda inside the function of ViewModelProvider.Factory. Now we can do:

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

    viewModel = createViewModel { MyViewModel() }
}

a glance at the Collections API

We can use methods such as map, filter, groupBy and so on.

Let me show you an actual use-case where I had a List<Event> that needed to be converted to Map<Long, List<Event>> where each event was separated into a separate list and grouped by their start date.

val calendar = Calendar.getInstance()
val dateEventMap = eventList.groupBy { event ->
    calendar.timeInMillis = event.startTimeMillis
    calendar.resetHoursMinutesSecondsMillis()
    calendar.timeInMillis
}

It's worth knowing that if you catch yourself iterating a list, collecting items by key into a map; then you should be using groupBy which does this for you in a single method call.

There are many such similar functions in the Collections API and I urge you to check them out.

inline fun <reified T>

In Java, there are times when you must pass a Class<T> to a given method.

RealmQuery<MyObject> query = realm.where(MyObject.class);

In Kotlin though, this typically looks like

val query = realm.where(MyObject::class.java)

Which looks very tacky!

The good news is that we can define an inline fun <reified T> method which behind the scenes passes the Class object for us, while we can invoke it as a regular extension function. This means that we can access T::class.java if the template variable is reified. I'll show you what I mean.

inline fun <reified T: RealmModel> Realm.where(): RealmQuery<T> = this.where(T::class.java)

And now you can use it like this:

val query = realm.where<MyObject>()

Or like this

val query: RealmQuery<MyObject> = realm.where()

Personally, whenever I see a ::class.java, I wonder "can I make an inline fun <reified T> for this?" and if I can, I make an extension function for it. In some cases you can't (typically if you need to pass the class to a constructor), but you should where you can.

sealed classes

I've been waiting for this, sealed class in Kotlin is a very powerful construct. Although you could just say "enums on steroids".

This means that you can create regular classes (data class, object) that extend from a given base-class, and it is enforced that these are the only possible implementations of this base-class.

This is important because it means you can use exhaustive when statements over them.

sealed class Message {
    data class TextMessage(val text: String): Message()
    data class PictureMessage(val image: ByteArray): Message()
}

and now we can actually use when for a message instance, and check its type. No more ugly instanceofs, and no need for adding a Visitor just to avoid instanceofs; and no need to add an abstract fun where you don't really need it.

val message = ...
when(message) {
   is TextMessage -> println("${message.text}")
   is PictureMessage -> sendToFriends(message.image)
}.safe()

Note that it's common to use object in place of no-arg data class even when using sealed classes, but it has a different toString() and therefore I prefer the trick mentioned at the data classes section.

infix keyword

Another interesting tidbit (although in my opinion, one of the most over-abused ones) is the ability to define infix functions.

Infix means that if you have a function that takes only 1 argument, then you can replace the function with a different syntax. Check the example below.

val pair: Pair<String, Int> = name.to(age) // not infix

val pair: Pair<String, Int> = name to age // infix

Basically it lets you throw out the .__(__) and call the function as "if it was a language construct / keyword". This is also how until is implemented.

tuples (to) and destructuring

In the above example, I showed that to exists to create Pair<S, T>, and there's actually also a Triple<S, T, U>.

What's really nice is that tuples such as Pair or Triple can be "destructured" by index.

You can actually destructure any data classes, because they all get component1...componentN methods, although that is generally not recommended. Primarily prefer destructuring only for classes meant to be used as tuples.

Here is how it looks.

val triple = Triple(name, age, birthdate)

val (name, age, birthdate) = triple

You may ask "hold on how is this useful?" but there are actual use-cases where creating a class with named arguments is excessive, and we could just use a tuple instead.

fun <T1, T2> Observable<T1>.combineWith(other: Observable<T2>): Observable<Pair<T1, T2>> =
    Observable.combineLatest(this, other, BiFunction<T1, T2, Pair<T1, T2>> { t1, t2 -> t1 to t2 })

which now I can use as

firstObservable.combineWith(secondObservable)
               .subscribeBy(onNext = { (firstValue, secondValue) ->
                   ...
               })

extension operators (componentN)

An interesting tidbit about destructuring is that it uses functions called operator fun componentN.

If these are added as extension functions to a given class, then we gain the ability to destructure it.

// from Android-KTX at https://github.com/android/android-ktx/blob/136ba4cdb3b6ece7470cbddeaf6a168021a69a30/src/main/java/androidx/core/graphics/Color.kt
inline val @receiver:ColorInt Int.alpha get() = (this shr 24) and 0xff
inline val @receiver:ColorInt Int.red get() = (this shr 16) and 0xff
inline val @receiver:ColorInt Int.green get() = (this shr 8) and 0xff
inline val @receiver:ColorInt Int.blue get() = this and 0xff

inline operator fun @receiver:ColorInt Int.component1() = (this shr 24) and 0xff
inline operator fun @receiver:ColorInt Int.component2() = (this shr 16) and 0xff
inline operator fun @receiver:ColorInt Int.component3() = (this shr 8) and 0xff
inline operator fun @receiver:ColorInt Int.component4() = this and 0xff

// ------

val (alpha, red, blue, green) = Color.parseString("#2ABCBC")