A life without ifs

No more ifs? That sounds great! Well with Kotlin that is possible. Kind of. Kotlin has multiple language features that can replace ifs in many cases. But are they always a good idea? Let’s find out.

Let’s take a look at an if statement you probably wrote in the past if you’re an Android developer:

override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == R.id.action_settings) {
// do something
return true
} else if (item.itemId == R.id.action_info) {
// do something else
return true
} else {
return super.onOptionsItemSelected(item)
}
}

Android Studio will already highlight that this can be replaced by a when statement:

override fun onOptionsItemSelected(item: MenuItem): Boolean {
when {
item.itemId == R.id.action_settings -> {
// do something
return true
}
        item.itemId == R.id.action_info -> {
// do something else
return true
}
        else -> return super.onOptionsItemSelected(item)
}
}

It will even further highlight that you can lift the return statement out of the when statement.

override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when {
item.itemId == R.id.action_settings -> {
// do something
true
}
        item.itemId == R.id.action_info -> { 
// do something else
true
}
        else -> super.onOptionsItemSelected(item)
}
}

But there’s another thing we can do — we always check item.itemId in the when statement, which becomes quite repetitive if you have a lot of if clauses. Luckily we can extract that into the when statement:

override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when(item.itemId) {
R.id.action_settings -> {
// do something
true
}
        R.id.action_info -> { 
// do something else
true
}
        else -> super.onOptionsItemSelected(item)
}
}

Now that seems nice, but that’s not that much simpler than the if statement and is just like a switch statement in Java, right? But when comes with a few more advantages. So let’s take a look at another example. This is a very simplified version of what your onBindViewHolder method in a RecyclerView adapter might look like:

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is HeaderViewHolder -> holder.headerView.text = "Header"
is
ItemViewHolder -> holder.itemView.text = "Item"
is
FooterViewHolder -> holder.footerView.text = "Footer"
}
}

The when statement is automatically casting the ViewHolder. This would also cast a nullable item to non-null. There’s quite a few more things that when can do — check out the Kotlin docs.

So why would I ever want to use if instead? when is great, but there’s still a few cases where a small if statement might be better. Take a look at this for example:

fun doSomething(content: String?) {
if (content != null) doThis(content) else doThat()
}

Converting this into a when statement not only makes it longer, but in my opinion also less clear:

fun doSomething(content: String?) {
when {
content != null -> doThis(content)
else -> doThat()
}
}

So there’s still a few cases where we want to use if instead of when. If you’re unsure a good place to check are the Kotlin coding conventions.

Right, so that’s when covered. But what else can we do?

Let’s take a look at this example:

fun updateText(content: String?) {
textView.text = if (content != null) content else "No content"
}

We can simplify this using an elvis expression:

fun updateText(content: String?) {
textView.text = content ?: "No content"
}

So now “No content” will be used whenever content is null. But what if the TextView is null?

fun updateText(content: String?) {
if (textView != null) {
textView!!.text = content ?: "No content"
}
}

That’s not very nice — and because textView is mutable we have to use !! to assert this is not null. But there’s a simpler way to do that:

fun updateText(content: String?) {
textView?.text = content ?: "No content"
}

The ? does the null check for us. Easy! But what if you want to set multiple fields on the view? Well you could just keep doing what you were doing:

fun updateText(content: String?) {
textView?.text = content ?: "No content"
textView
?.setTextColor(Color.BLACK)
}

But there’s another way:

textView?.let {
it
.text = content ?: "No content"
it
.setTextColor(Color.BLACK)
}

Great! So let’s say we want to update the view visibility depending on content being available. Well we could do this:

textView?.let {
it
.text = content ?: "No content"
it
.setTextColor(Color.BLACK)
it.visibility = if (content != null) View.VISIBLE else View.GONE
}

Oh no! An if statement. We could obviously convert this into a when statement, but that’s against the coding conventions. So what else can we do? Well we have two statements that check if the content is null. So we can use another let!

fun updateText(content: String?) {
textView?.let { tv ->
content?.let {
tv.text = it
tv.setTextColor(Color.BLACK)
tv.visibility = View.VISIBLE
} ?: let { tv.visibility = View.GONE }
}
}

Ok, this is getting confusing (and quite long)! We have two nested let and the ?: let at the end. What does that even do? Well this is executed when content is null. Not very obvious and very simple to miss the difference between ?: and ?. — there must be a better way to do this! Extension functions to the rescue!

fun updateText(content: String?) {
textView?.updateOrHide(content) {
text = it
setTextColor(Color.BLACK)
}
}

private inline fun <T> TextView.updateOrHide(content: T?, action: TextView.(content: T) -> Unit) {
if (content != null) {
visibility = View.VISIBLE
action(content)
} else {
visibility = View.GONE
}
}

Right, now all the difficult part is hidden away in an extension function. There’s still an if, but it’s hidden away (and could be converted to a when or let if you’d really want to. But what’s actually happening here? With TextView.updateOrHide we’re creating an extension functions on TextView. This means we can add a method to TextView without having to extend the class. In Java this would have to be called via a static method.

Next up we have this action: TextView.(content: T) -> Unit. This means that the updateOrHide function takes another function as a parameter — whenever we call the function in our code we can perform a different action. Within updateOrHide we call this function as action(content). This function is (again) an extension function, passing through the content (but this time not-nullable) and returning Unit aka void in Java.

You might’ve also noticed the inline part. This is for memory reasons — without that each high order function (as in the ones with a function passed through) is an object. Using inline will tell the compiler to inline the function — basically generating this code:

if (content != null) {
visibility = View.VISIBLE
text
= content
setTextColor(Color.BLACK)
} else {
visibility = View.GONE
}

Ok, now that we know what’s going on there, let’s take a look at the code calling our extension function again:

fun updateText(content: String?) {
textView?.updateOrHide(content) {
text = it
setTextColor(Color.BLACK)
}
}

What is it in this case? And what are we setting the text on? In my opinion it’s not very clear. Especially if the extension function lives somewhere else (e.g. in a separate file where you have useful extensions declared) it needs to be clear what happens whenever you call it. So maybe we went a level too far here.

It’s very easy to go overboard when finding out about new things — the same goes for when and let — in the beginning you might be tempted to use them for everything, but there’s always places where the new shiny thing won’t work well. A good way to find out if something works is a code review — if the reviewer has difficulties figuring out what’s going on it might not be the best solution.

So yes — Kotlin without ifs might be possible, but I wouldn’t recommend it. But often your code can be simplified a lot by using some of Kotlin’s more modern language features. Give it a try!