Using scope functions apply/with/run/also/let
Opened this issue ยท 7 comments
apply
Use apply
for initialization:
val foo = createBar().apply {
property = value
init()
}
also
Use also
over apply
if the receiver is used for anything other than setting properties or function calls on it:
class Baz {
var currentBar: Bar?
val observable: Observable
val foo = createBar().also {
currentBar = it
observable.registerCallback(it)
}
}
Prefer also
over apply
if there already are multiple receivers in scope, especially if you make calls on any outer receivers:
class Foo {
fun Bar.baz() {
val stuff = callSomething().also {
it.init()
this@baz.registerCallback(it)
}
}
}
apply/with/run
Prefer apply
/run
over with
if the receiver is nullable.
getNullable()?.run {
init()
}
getNullable()?.apply {
init()
}
Prefer run
/with
over apply
if the returned value is not used
view.run {
textView.text = "Hello World"
progressBar.init()
}
with(view) {
textView.text = "Hello World"
progressBar.init()
}
Choose one of the above and use it consistently.
let
Prefer let
over run
in method chains that transform the receiver
val baz: Baz = foo.let { createBar(it) }.convertBarToBaz()
// or with function references
val baz: Baz = foo.let(::createBar).convertBarToBaz()
I use with
over run
if the object in question is a local variable or a short expression:
with(view) {
textView.text = "Hello World"
progressBar.init()
}
On the other hand, I use run
in the end of a long call chain:
MyApp.defaultActivity.getActiveView().view.run {
textView.text = "Hello World"
progressBar.init()
}
@cypressious Why you prefer run
over apply
if the returned value is not used? run
is also returning value - the result of function literal. The same with with
, but for this one, I also feel intuitively that it should be used this way.
I use let
when I just need to unpack nullable read-write property (when smart-casting is not working):
var text: String? = null
fun showText() {
text?.let { text ->
println(text)
}
}
I often see let
used this way.
@MarcinMoskala it's just to differentiate. I use apply
when I need to the receiver as the return value, otherwise I don't use it.
Another good use of with
is for executing a lambda in the context of a casted object:
with(activity as MyActivity) {
myActivitymethod()
...
}
Using also
for a "singleton with parameter" (from Google's architecture components sample code):
class UsersDatabase : RoomDatabase() {
companion object {
@Volatile private var INSTANCE: UsersDatabase? = null
fun getInstance(context: Context): UsersDatabase =
INSTANCE ?: synchronized(this) {
INSTANCE ?: buildDatabase(context).also { INSTANCE = it }
}
private fun buildDatabase(context: Context) =
Room.databaseBuilder(context.applicationContext,
UsersDatabase::class.java, "Sample.db")
.build()
}
}
I was trying out what to use when in the same year when this issue was created. My understanding back then is that these functions should be used based on intention:
- apply: post-construction configuration
- with: configure object created somewhere else
- also: additional processing on an object in a call chain
- let: conversion of value
- run: execute lambda with side-effects and no result