Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Idiomatic way of handling nullable or empty List in Kotlin

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?

like image 271
Kirill Rakhman Avatar asked Oct 13 '14 13:10

Kirill Rakhman


People also ask

What is the idiomatic way to deal with nullable values referencing or converting them?

sure operator. The !! operator asserts that the value is not null or throws an NPE. This should be used in cases where the developer is guaranteeing that the value will never be null .

How do you handle nullable in Kotlin?

Kotlin has a safe call operator (?.) to handle null references. This operator executes any action only when the reference has a non-null value. Otherwise, it returns a null value. The safe call operator combines a null check along with a method call in a single expression.


2 Answers

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 } 
like image 127
13 revs, 2 users 92% Avatar answered Sep 19 '22 16:09

13 revs, 2 users 92%


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 } 
like image 38
user4097210 Avatar answered Sep 18 '22 16:09

user4097210