Kotlin apply function


In continuation to my previous post where I explained about Kotlin let function, let's try to understand today about apply function today.

Just to recap, Scope functions are nothing but the functions which define to the scope of the calling object. We can apply operations on that object within that scope and return the object itself from that scope function or we can even return the result of operation or operations from the scope function.
There are a few scope functions
To keep this article short and to the point, we will talk only about apply in this article and all the use cases around it.

apply scope function is used to configure the object once initialized and returns the object itself. The context object is available inside the apply function as this.

To understand apply function lets look at the implementation of apply function first.
  
/**
* Calls the specified function [block]
* with `this` value as its receiver
* and returns `this` value.
*
*/
@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
return this
}

apply is an extension function to Template class which takes a lambda as a parameter, apply contract on it, execute the lambda function within the scope of calling object and ultimately return the same calling object of Template class itself.

This clarifies a few things

  1. The return type of the apply function is nothing but the same calling object.
  2. Since its an extension function to the Template class, it can be called on any object.

Now let's understand what is the contract

The contract is nothing but a contract applied to the passed lambda as a parameter.
  
/**
* Specifies the contract of a function.
*
* The contract description must be at the beginning of a function and have at least one effect.
*
* Only the top-level functions can have a contract for now.
*
* @param builder the lambda where the contract of a function is described with the help of the [ContractBuilder] members.
*
@ContractsDsl
@ExperimentalContracts
@InlineOnly
@SinceKotlin("1.3")
@Suppress("UNUSED_PARAMETER")
public inline fun contract(builder: ContractBuilder.() -> Unit) { }
This is exactly the same contract as to any other scope function. This superimposes some conditions on the lambda we passed as a parameter to the apply function. What conditions it superimposed, we need to check the parameter of the contract

And what contract applied in the apply function ?

  
/**
* Specifies that the function parameter [lambda] is invoked in place.
*
* This contract specifies that:
* 1. the function [lambda] can only be invoked during the call of the owner function,
* and it won't be invoked after that owner function call is completed;
* 2. _(optionally)_ the function [lambda] is invoked the amount of times specified by the [kind] parameter,
* see the [InvocationKind] enum for possible values.
*
* A function declaring the `callsInPlace` effect must be _inline_.
*
*/
/* @sample samples.contracts.callsInPlaceAtMostOnceContract
* @sample samples.contracts.callsInPlaceAtLeastOnceContract
* @sample samples.contracts.callsInPlaceExactlyOnceContract
* @sample samples.contracts.callsInPlaceUnknownContract
*/
@ContractsDsl public fun callsInPlace(lambda: Function, kind: InvocationKind = InvocationKind.UNKNOWN): CallsInPlace
It superimposes 2 conditions
  1. The lambda will be invoked only during owner function call and it won't be called once the owner function is completed.
  2. The number of times this lambda function will be invoked (which is exactly once in our case) which is an enum.
The above conditions are clear from there definition itself.

So basically, apply function will be

  • called only during the owner function will be called.
  • called ONLY ONCE.
  • called on the calling object.
  • and will return the object itself on which the apply function is called.

Now let's look at the use cases

apply is recommended for lambdas that mainly operate on the object members or call its functions or assign properties. In short, it is mostly used for object configurations.
  
class Employee {
var firstName: String = ""
var age: Int = 0
}

val employee = Employee().apply{
firstName = "Suneet Agrawal"
age = 27
}

println("name:${employee.firstName} age:${employee.age}" )

Please note that we can even point to the calling object by this but we can't use a named parameter in apply.
  
val employee = Employee().apply{
this.firstName = "Suneet Agrawal"
this.age = 27
}

We can avoid the use of this pointer or use it to avoid the conflicts between other properties or objects with the same name within that class. It points to the calling object only.

There is no need to call the return from apply function. Although there won't be any compilation error. Since the return type of the lambda passed in apply function is Unit, no point of calling return.
  
val employee = Employee().apply{
this.firstName = "Suneet Agrawal"
this.age = 27
return //this line doesn't make sense at all
}
We can even chain the apply function
  
class Employee {
var firstName: String = ""
var age: Int = 0

fun somefunction(){
//do something here
}
}

Employee().apply{
this.firstName = "Suneet Agrawal"
this.age = 27
}.somefunction()

Two things to keep in mind about apply,

  1. apply uses the context as this and we can not use a named parameter instead of this.
  2. apply returns the calling object itself.


Reference: Kotlin docs