Use Cases of Kotlin’s apply, also, let, with and run

There are so many articles to explain their differences and how to select one by each use case. Here I just simply share our team’s consensus on the usages of these functions.

Bram Yeh
4 min readFeb 3, 2019

TL;DR

  • apply
    we have the object T and want to apply more post-addition configuration to T itself.
  • also
    we have the object T and also want to do something which been getting through T.
  • let
    use let for transformations or null check when the context object T is treated as an Object in the block.
  • run
    use run for transformations or null check when the context object T is treated as a Subject in the block.
  • with
    we know the context object T is non-null, and we need to access directly.

In the first place, I would like to share these great articles which explained these functions, e.g

These articles already explain these functions in detail, so I just want to share their use cases and the reason why our team prefers certain one function in that case. Generally, they are useful to replace the null checks, to execute blocks if this context object isn’t null, so here I will skip this kind of use case.

Let’s start from apply and also, both are useful and frequently used, especially during initialization. But they are slightly different, apply is used to initialize the object itself, and also is doing things through the object. In the opinion of our team, we tend towards semantics more than syntax.

  • apply <T> T.apply(block: T.() -> Unit): T

It’s useful when we have or create the object T and want to apply more post-addition configuration to T itself. Inside the block, we would focus on the set up of the context object. Since apply returns the receiver in the end, it’s very convenient for the initialization:

view.findViewById<TextView>(R.id.info)?.apply {
text = “”
visibility = View.GONE
}
author.animate().apply {
translationX(0f)
duration = 200L
interpolator = moveInterpolator
}
  • also <T> T.also(block: (T) -> Unit): T

This one is that we have an object T and also want to do something related and been getting through T, it is more convenient to complete together. In this case, also is very similar to “by the way.”

For example, android Fragment inflates a root view at onCreateView(), and usually, we don’t only inflate the main view for our fragment but also get other subviews by findViewById(). But, by the way, we apply the default values to these subviews:

private lateinit var titleView: TextView
private lateinit var button: Button
private lateinit var recyclerView: RecyclerView
override fun onCreateView(inflater, container, savedInstanceState): View? {
return inflater.inflate(R.layout.fragment_store_profile, container, false)?.also {
titleView = it.findViewById<TextView>(R.id.title).apply {
text = "Welcome"
}
button = it.findViewById<Button>(R.id.action).apply {
text = "Exit"
visibility = View.INVISIBLE
}
recyclerView = view.findViewById<RecyclerView>(R.id.product_list).apply {
layoutManager = LinearLayoutManager(activity)
addItemDecoration(ProductItemDecoration())
}
}
}

And now let’s explore other functions, let, with and run. All three functions return another value from blocks, which means they are suitable for transformations. In general, there is no particular distinction between let and run for transformations, but if the context object is treated as an Object in the block, use let is more intuitional, otherwise; if it is treated as a Subject, we will prefer to use run.

  • let <T, R> T.let(block: (T) -> R): R

It’s common to use let for transformations, there is a good example from Elye, and I just do a little modification: this function uses let to do null check and transform String to File, and then use also to create the directory:

fun makeDir(path: String?) = 
path?.let{ File(it) }.also{ it.mkdirs() } ?:
throw IllegalStateException("Invalid Dir Path!")

More often, we want to do some operations when we map a nullable type value, we will prefer to use let when the context object T is treated as an Object in the block, for example,

person.email?.let { sendEmail(it) }
  • with <T,R> with(receiver: T, block: T.() -> R): R

It’s seldom seen in our project. We can use with for transformation, but generally, we prefer let or run. However, when we know the context object T is non-null, and we need to access directly, it’s good to use with, for example,

with(employee) {
println("name:$employeeName, title:$jobTitle")
}
  • run has two different styles: extension function with receiver and function

a.) extension function with receiver <T, R> T.run(block: T.() -> R): R

Its use case seems like “I have won the stuff, I need to modify its essence to get my eventual result,” that’s why I regard T.run as T.apply + T.let. If context object T is treated as a Subject, we prefer to use run. There is an excellent example from kotlinexpertise:

val date: Int = Calendar.getInstance().run {
set(Calendar.YEAR, 2030)
get(Calendar.DAY_OF_YEAR) //return value of run
}

Like the example above, I set the year of Calendar instance to 2030 to get the day count. So I use T.run to modify it and transform it to day-of-year.

Moreover, there is one more use case I suggest to use run. It’s that we want to do a null check and then use the receiver, but we don’t wish the transform. Inside plaid, there is a good sample for this case:

private fun createBitmap() {
cutout?.run {
if (!isRecycled) {
recycle()
}
}
....

cutout = createBitmap(width, height).applyCanvas {
drawColor(foregroundColor)
drawText(text, textX, textY, textPaint)
}
}

b.) function <R> run(block: () -> R): R

I only use this run to break out of forEach. Kotlin’s return expression returns from the nearest enclosing function, that’s means inside forEach there is no direct equivalent for break. The only workaround is adding another nesting function and returning by label:

var isExisted = falserun loop@{
stores?.forEach {
if (storeCache.contains(it)) {
isExisted = true
return@loop // this return@loop will break the forEach
}
}
}

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Bram Yeh
Bram Yeh

Written by Bram Yeh

Lead Android & iOS Mobile Engineer at Yahoo (Verizon Media) Taiwan https://www.linkedin.com/in/hanruyeh/

No responses yet

Write a response