Creating a List in Kotlin based on Conditions

Viewed 970

At the moment I have this fun:

private fun validateArguments(city: String, state: String, country: String, zip: String): List<String> {

        val list: MutableList<String> = mutableListOf()
        if (city.isNullOrBlank()) list.add("Invalid city")
        if (state.isNullOrBlank()) list.add("Invalid state")
        if (country.isNullOrBlank()) list.add("Invalid country")
        if (zip.isNullOrBlank()) list.add("Invalid zip code")

        return list.toList()
}

and I was wondering if there a more elegent way to create the list. The final list could also be a MutableList I guess.

3 Answers

If you're using Kotlin 1.6+, there are builder APIs for lists, sets, and maps:

val list = buildList {
    if (city.isNullOrBlank()) add("Invalid city")
    if (state.isNullOrBlank()) add("Invalid state")
    if (country.isNullOrBlank()) add("Invalid country")
    if (zip.isNullOrBlank()) add("Invalid zip code")
}

This allows to limit the scope of the mutable handle to the list, and only expose the read-only interface. It also fits nicely in extracted methods.

I'm not sure there's a single, obviously better way; it's already fairly concise and clear.

But here's one alternative, using the listOfNotNull and takeIf functions to avoid an explicit temporary list:

private fun validateArguments(city: String, state: String, country: String, zip: String)
    = listOfNotNull(
        "Invalid city".takeIf{ city.isNullOrBlank() },
        "Invalid state".takeIf{ state.isNullOrBlank() },
        "Invalid country".takeIf{ country.isNullOrBlank() },
        "Invalid zip code".takeIf{ zip.isNullOrBlank() })

Here takeIf() returns null if the condition isn't met; and listOfNotNull() then removes all those nulls, leaving only the errors for those which were met.

You can't do too much to make it shorter without some crazy solutions (e.g. reflection). If all params are always strings and error messages are always very similar, you can remove some redundant code with:

@OptIn(ExperimentalStdlibApi::class)
private fun validateArguments2(city: String, state: String, country: String, zip: String) = buildList {
    for ((value, name) in listOf(
        city to "city",
        state to "state",
        country to "country",
        zip to "zip code"
    )) {
        if (value.isBlank()) {
            add("Invalid $name")
        }
    }
}

But it doesn't make too much sense if you don't have a big amount of these params.

Also, you don't need to check for nulls (isNullOrBlank()) if your params are not even nullable.

Update

Or without experimental APIs:

private fun validateArguments2(city: String, state: String, country: String, zip: String) : List<String> {
    return listOf(
        city to "city",
        state to "state",
        country to "country",
        zip to "zip code"
    ).filter { it.first.isBlank() }
        .map { "Invalid ${it.second}" }
}

But it will be more enigmatic for a reader than your original implementation, so you really need to have a reason to do this.

Related