Search code examples
kotlinvariadic-functions

Can Kotlin filter out null arguments when passed to a varargs parameter?


This function in kotlin expects zero or more pairs of properties.

fun trackEvent(vararg properties: Pair<Any?, Any?>){
   ...
}

Usually it is called like this and works great

trackEvent( 
            "time" to timeStr,
            "user" to user
          )

where timeStr and user are nullable Strings

So far so good, but this requires us to manually pass in all the variables individually to the tracking function whereas it would be easier to just pass in an object. To solve this, I created a function that takes in a Kotlin class and outputs an array of Pair objects

fun Any.toArrOfPairs(): Array<Pair<Any?, Any?>>
{
  ...
}

and wish to use it like this

trackEvent(
            *source?.toArrOfPairs()
          )

There's a compiler error; however:

compiler error

It seems like kotlin is worried about the case where source is null. Is there an elegant to tell it to call trackEvent with an empty array in this case?

Similarly, if we combine approaches and call trackEvent as below, is there a way to tell kotlin "if source is null, just pass in the other arguments (in this case - the "time" pair)

trackEvent(
               "time" to timeStr,
                *source?.toArrOfPairs()
              )

Solution

  • Make your helper function accept a nullable receiver and return an empty array for the null case:

    fun Any?.toArrOfPairs(): Array<Pair<Any?, Any?>>
    {
      this ?: return emptyArray()
      // Your original code ...
    }
    

    Then skip the null-safe call when using it, because it's no longer necessary:

    trackEvent(*source.toArrOfPairs())
    

    I'm not sure if I understand the last part of your question exactly. If you want to combine explicit arguments and an array, you can:

    trackEvent(
        "time" to timeStr,
        *source.toArrOfPairs()
    )
    

    If you wanted to only use the explicit arguments if the source is null, you could use the Elvis operator, but it's a bit ugly because then you have to put the explicit arguments in an array:

    trackEvent(
        *(source?.toArrOfPairs() ?: arrayOf("time" to timeStr))
    )
    

    Or just use an if statement. Maybe more verbose, but easier to read:

    if (source != null) {
        trackEvent(*source.toArrOfPairs())
    } else {
        trackEvent(
            "time" to timeStr
        )
    }