Reputation: 43821
Say I have a variable activities
of type List<Any>?
. If the list is not null and not empty, I want to do something, otherwise I want to do something else. I came up with following solution:
when {
activities != null && !activities.empty -> doSomething
else -> doSomethingElse
}
Is there a more idiomatic way to do this in Kotlin?
Upvotes: 54
Views: 81445
Reputation: 199
Another solution would be to use ?.firstOrNull()?.
(optionally with the Elvis operator ?:
) like follows:
val mvList : List<Int?>? = when ((0..2).random()) {
0 -> arrayListOf(1, 2, 3).filter{it == 3} //Assigns a list containing one element, the integer 3.
1 -> arrayListOf(1, 2, 3).filter{it == 4} //Assigns an empty list.
else -> null //Assigns null.
}
mvList?.firstOrNull()?.also{println("$mvList is NEITHER null NOR empty!")} ?: println("$mvList is EITHER empty OR null!")
Upvotes: 1
Reputation: 3224
More simplest way would be,
if(activities?.isNotEmpty() == true) doSomething() else doSomethingElse()
Because of the safe call on activities?
, the return value is Boolean?
which can either be true
, false
or null
. In order to use this expression in an if, we need to explicitly check if it's true
Upvotes: 12
Reputation: 21
Kotlin 1.3 has extension isNullOrEmpty. The brief answer is:
if (activities.isNullOrEmpty) doSomething
else doSomethingElse
Extension is defined as:
fun <T> Collection<T>?.isNullOrEmpty(): Boolean
Similar extension exists for String and Array.
Upvotes: 2
Reputation: 14611
The actual method to use in Kotlin 1.3 is isNullOrEmpty
like what was mentioned in this answer: https://stackoverflow.com/a/48056456/2735286
Here is an example of its usage:
fun main(args: Array<String>) {
var array: MutableList<String>? = null
println(array.isNullOrEmpty()) // true
array = mutableListOf()
println(array.isNullOrEmpty()) // true
array = mutableListOf("a")
println(array.isNullOrEmpty()) // false
}
This example prints out:
true
true
false
Upvotes: 3
Reputation: 11779
In my case prices is a optional.I handle the case in the following way with orEmpty()
which returns the given array or an empty array if the given array is null.
val safeArray = poi.prices.orEmpty()
if (!safeArray.isEmpty()) {
...
}
Upvotes: 0
Reputation: 1829
UPDATE:
kotlin 1.3 provide isNullOrEmpty
now!
https://twitter.com/kotlin/status/1050426794682306562
try this! very clear.
var array: List<String>? = null
if (array.orEmpty().isEmpty()) {
// empty
} else {
// not empty
}
Upvotes: 50
Reputation: 85946
For some simple actions you can use the safe call operator, assuming the action also respects not operating on an empty list (to handle your case of both null and empty:
myList?.forEach { ...only iterates if not null and not empty }
For other actions. you can write an extension function -- two variations depending on if you want to receive the list as this
or as a parameter:
inline fun <E: Any, T: Collection<E>> T?.withNotNullNorEmpty(func: T.() -> Unit): Unit {
if (this != null && this.isNotEmpty()) {
with (this) { func() }
}
}
inline fun <E: Any, T: Collection<E>> T?.whenNotNullNorEmpty(func: (T) -> Unit): Unit {
if (this != null && this.isNotEmpty()) {
func(this)
}
}
Which you can use as:
fun foo() {
val something: List<String>? = makeListOrNot()
something.withNotNullNorEmpty {
// do anything I want, list is `this`
}
something.whenNotNullNorEmpty { myList ->
// do anything I want, list is `myList`
}
}
You can also do inverse function:
inline fun <E: Any, T: Collection<E>> T?.withNullOrEmpty(func: () -> Unit): Unit {
if (this == null || this.isEmpty()) {
func()
}
}
I would avoid chaining these because then you are replacing an if
or when
statement with something more wordy. And you are getting more into the realm that the alternatives I mention below provide, which is full branching for success/failure situations.
Note: these extensions were generalized to all descendants of Collections
holding non null values. And work for more than just Lists.
Alternatives:
The Result library for Kotlin gives a nice way to handle your case of "do this, or that" based on response values. For Promises, you can find the same thing in the Kovenant library.
Both of these libraries give you manner for returning alternative results from a single function, and also for branching the code based on the results. They do require that you are controlling the provider of the "answer" that is acted upon.
These are good Kotlin alternatives to Optional
and Maybe
.
Exploring the extension Functions Further (and maybe too much)
This section is just to show that when you hit an issue like the question raised here, you can easily find many answers in Kotlin to make coding the way you want it to be. If the world isn't likeable, change the world. It isn't intended as a good or bad answer, but rather additional information.
If you like the extension functions and want to consider chaining them in an expression, I would probably change them as follows...
The withXyz
flavours to return this
and the whenXyz
should return a new type allowing the whole collection to become some new one (maybe even unrelated to the original). Resulting in code like the following:
val BAD_PREFIX = "abc"
fun example(someList: List<String>?) {
someList?.filterNot { it.startsWith(BAD_PREFIX) }
?.sorted()
.withNotNullNorEmpty {
// do something with `this` list and return itself automatically
}
.whenNotNullNorEmpty { list ->
// do something to replace `list` with something new
listOf("x","y","z")
}
.whenNullOrEmpty {
// other code returning something new to replace the null or empty list
setOf("was","null","but","not","now")
}
}
Note: full code for this version is at the end of the post (1)
But you could also go a completely new direction with a custom "this otherwise that" mechanism:
fun foo(someList: List<String>?) {
someList.whenNullOrEmpty {
// other code
}
.otherwise { list ->
// do something with `list`
}
}
There are no limits, be creative and learn the power of extensions, try new ideas, and as you can see there are many variations to how people want to code these type of situations. The stdlib cannot support 8 variations of these type of methods without being confusing. But each development group can have extensions that match their coding style.
Note: full code for this version is at the end of the post (2)
Sample code 1: Here is the full code for the "chained" version:
inline fun <E: Any, T: Collection<E>> T?.withNotNullNorEmpty(func: T.() -> Unit): T? {
if (this != null && this.isNotEmpty()) {
with (this) { func() }
}
return this
}
inline fun <E: Any, T: Collection<E>, R: Any> T?.whenNotNullNorEmpty(func: (T) -> R?): R? {
if (this != null && this.isNotEmpty()) {
return func(this)
}
return null
}
inline fun <E: Any, T: Collection<E>> T?.withNullOrEmpty(func: () -> Unit): T? {
if (this == null || this.isEmpty()) {
func()
}
return this
}
inline fun <E: Any, T: Collection<E>, R: Any> T?.whenNullOrEmpty(func: () -> R?): R? {
if (this == null || this.isEmpty()) {
return func()
}
return null
}
Sample Code 2: Here is the full code for a "this otherwise that" library (with unit test):
inline fun <E : Any, T : Collection<E>> T?.withNotNullNorEmpty(func: T.() -> Unit): Otherwise {
return if (this != null && this.isNotEmpty()) {
with (this) { func() }
OtherwiseIgnore
} else {
OtherwiseInvoke
}
}
inline fun <E : Any, T : Collection<E>> T?.whenNotNullNorEmpty(func: (T) -> Unit): Otherwise {
return if (this != null && this.isNotEmpty()) {
func(this)
OtherwiseIgnore
} else {
OtherwiseInvoke
}
}
inline fun <E : Any, T : Collection<E>> T?.withNullOrEmpty(func: () -> Unit): OtherwiseWithValue<T> {
return if (this == null || this.isEmpty()) {
func()
OtherwiseWithValueIgnore<T>()
} else {
OtherwiseWithValueInvoke(this)
}
}
inline fun <E : Any, T : Collection<E>> T?.whenNullOrEmpty(func: () -> Unit): OtherwiseWhenValue<T> {
return if (this == null || this.isEmpty()) {
func()
OtherwiseWhenValueIgnore<T>()
} else {
OtherwiseWhenValueInvoke(this)
}
}
interface Otherwise {
fun otherwise(func: () -> Unit): Unit
}
object OtherwiseInvoke : Otherwise {
override fun otherwise(func: () -> Unit): Unit {
func()
}
}
object OtherwiseIgnore : Otherwise {
override fun otherwise(func: () -> Unit): Unit {
}
}
interface OtherwiseWithValue<T> {
fun otherwise(func: T.() -> Unit): Unit
}
class OtherwiseWithValueInvoke<T>(val value: T) : OtherwiseWithValue<T> {
override fun otherwise(func: T.() -> Unit): Unit {
with (value) { func() }
}
}
class OtherwiseWithValueIgnore<T> : OtherwiseWithValue<T> {
override fun otherwise(func: T.() -> Unit): Unit {
}
}
interface OtherwiseWhenValue<T> {
fun otherwise(func: (T) -> Unit): Unit
}
class OtherwiseWhenValueInvoke<T>(val value: T) : OtherwiseWhenValue<T> {
override fun otherwise(func: (T) -> Unit): Unit {
func(value)
}
}
class OtherwiseWhenValueIgnore<T> : OtherwiseWhenValue<T> {
override fun otherwise(func: (T) -> Unit): Unit {
}
}
class TestBrancher {
@Test fun testOne() {
// when NOT null or empty
emptyList<String>().whenNotNullNorEmpty { list ->
fail("should not branch here")
}.otherwise {
// sucess
}
nullList<String>().whenNotNullNorEmpty { list ->
fail("should not branch here")
}.otherwise {
// sucess
}
listOf("a", "b").whenNotNullNorEmpty { list ->
assertEquals(listOf("a", "b"), list)
}.otherwise {
fail("should not branch here")
}
// when YES null or empty
emptyList<String>().whenNullOrEmpty {
// sucess
}.otherwise { list ->
fail("should not branch here")
}
nullList<String>().whenNullOrEmpty {
// success
}.otherwise {
fail("should not branch here")
}
listOf("a", "b").whenNullOrEmpty {
fail("should not branch here")
}.otherwise { list ->
assertEquals(listOf("a", "b"), list)
}
// with NOT null or empty
emptyList<String>().withNotNullNorEmpty {
fail("should not branch here")
}.otherwise {
// sucess
}
nullList<String>().withNotNullNorEmpty {
fail("should not branch here")
}.otherwise {
// sucess
}
listOf("a", "b").withNotNullNorEmpty {
assertEquals(listOf("a", "b"), this)
}.otherwise {
fail("should not branch here")
}
// with YES null or empty
emptyList<String>().withNullOrEmpty {
// sucess
}.otherwise {
fail("should not branch here")
}
nullList<String>().withNullOrEmpty {
// success
}.otherwise {
fail("should not branch here")
}
listOf("a", "b").withNullOrEmpty {
fail("should not branch here")
}.otherwise {
assertEquals(listOf("a", "b"), this)
}
}
fun <T : Any> nullList(): List<T>? = null
}
Upvotes: 57
Reputation: 43821
In addition to the other answers, you can also use the safe-call operator in combination with the extension method isNotEmpty()
. Because of the safe call, the return value is actually Boolean?
which can either be true
, false
or null
. To use the expression in an if
or when
clause, you'll need to explictly check if it's true
:
when {
activities?.isNotEmpty() == true -> doSomething
else -> doSomethingElse
}
Alternative syntax using the elvis operator:
when {
activities?.isNotEmpty() ?: false -> doSomething
else -> doSomethingElse
}
Upvotes: 5
Reputation: 1174
Firstly I wanted to advise to make extension function in addition to @mlatu's answer, which handles else
condition
public inline fun Map.forEachElse(operation: (Map.Entry) -> Unit, elseBlock: () -> Unit): Unit {
if (!empty)
for (element in this) operation(element)
else
elseBlock()
}
But the usage is no so beautiful.
Actually you are looking for a Maybe monad
Upvotes: -1
Reputation: 1784
Consider using ?.forEach
if appropriate
activities?.forEach {
doSmth(it)
}
If you want exactly the behavior you described I think your variant reads better then anything else more concise I can think of. (Yet simple if
should suffice)
Upvotes: 3