5

I would like to reduce the size of an if-elseif-else ladder but can't use just when because the smartcast doesn't catch the cases I'm dealing with. I'm wondering if I can combine something like a let or an also and a when into a single inline function to solve this.

I've tried using smart casting with is but it's problematic because I'd have to do a when within the outer when to actually get the result I wanted. I ended up doing something similar to one of the responses from this post: Kotlin and idiomatic way to write, 'if not null, else...' based around mutable value by just having a let block acting on the non-null variable then executing the when block inside that let.

Case I'm currently going with:

variable?.let { safeVariable ->
    when {
        case1 -> doSomething(safeVariable)
        case2 -> doSomethingElse(safeVariable)
         ...
        else -> catchAll(safeVariable)
    }
    return@let
}
Log.e(TAG, "variable was null")

Cases I've considered:

when(variable) {
    is Type -> when {
                   case1 -> doSomething(variable)
                   case2 -> doSomethingElse(variable)
                   ...
                   else -> catchAll(variable)
               }
   else -> Log.e(TAG, "variable was null")
}
if (variable is Type) {
    when {
        case1 -> doSomething(variable)
        case2 -> doSomethingElse(variable)
         ...
        else -> catchAll(variable)
    }
} else {
    Log.e(TAG, "variable was null")
}

What I'd like to be able to write would look like this:

variable?.alsoWhen { safeVariable ->
    case1 -> doSomething(safeVariable)
    case2 -> doSomethingElse(safeVariable)
     ...
    else -> catchAll(safeVariable)
} ?: Log.e(TAG, "variable was null")

Is this possible to write in Kotlin with an extension function and if not is there at least an alternative to what I wrote above to make it more compact and readable?

EDIT:

Based on Damon's comment below I did think of a slightly cleaner way to approach this with:

when(true) { 
    variable?.case1 -> doSomething(variable) 
    variable?.case2 -> doSomethingElse(variable) 
     ... 
    variable is String -> catchAll(variable)
    else -> Log.e(TAG, "variable was null") 
} 

It would be nice to get rid of the (true) next to the when if possible but this is clean enough that I'm pretty happy with the solution.

Ryan Shea
  • 73
  • 4

1 Answers1

1

You can perform the cast inside the argument of the when statement.

E.g.

when (variable as? Type) {
    case1 -> ...
    case2 -> ...
    null -> ...
    else -> ...
}

Another example based on comment:

enum class UrlType { TYPE_1, TYPE_2, TYPE_3, NULL }

fun String?.urlType(): UrlType {
    return when {
        this?.contains("...") == true -> UrlType.TYPE_1
        this?.startsWith("...") == true -> UrlType.TYPE_2
        this != null -> UrlType.TYPE_3
        else -> UrlType.NULL
    }
}

when (variable.urlType()) {
    UrlType.TYPE_1 -> doSomething()
    UrlType.TYPE_2 -> doSomethingElse()
    UrlType.TYPE_3 -> ...
    UrlType.NULL -> ...
}
Damon Baker
  • 887
  • 7
  • 9
  • Deleted my comment because the formatting was bad and added it as an edit to the post. I think this is a good idea but my cases are actually function calls on a String variable (URL in this case) so it looks like `variable.contains("...")` or `variable.startswith("...")` for parsing a URL that represents a string. – Ryan Shea Apr 11 '19 at 21:25
  • 1
    That's tricky, the only improvement I can think of is delegating the responsibility of determining the type of URL to another function and representing the result as an enum or sealed class. – Damon Baker Apr 12 '19 at 02:56