kotlingoogle-codelab

How to avoid printing "null" when accessing a nullable property while using lambda within a lambda?


I have a Person class with a nullable hobby property. When I try to print the person's profile using a string template, it prints "null" if the hobby is null.

fun main() {
    val amanda = Person("Amanda", 33, null, null)
    val atiqah = Person("Atiqah", 28, "climb", amanda)

    amanda.showProfile()
    atiqah.showProfile()
}


class Person(val name: String, val age: Int, val hobby: String?, val referrer: Person?) {
    fun showProfile() {
        val getReferrerName: (Person?) -> String = {
            if (it != null) "Has a referrer named ${it.name} who likes to ${it.hobby} ."
            else "Doesn't have a referrer."
        }
        val getHobby: (String?) -> String = {
            if (it != null) "likes to $it"
            else "."
        }
        println("Name: ${name}\nAge: $age\n ${getHobby(hobby)} ${getReferrerName(referrer)}")
    }
}

For example, if the referrer's hobby is null, the output is:

Name: Amanda
Age: 33
 . Doesn't have a referrer.
Name: Atiqah
Age: 28
 likes to climb Has a referrer named Amanda who likes to null .

My Specific Question

I have a lambda getHobby that handles the null case for the hobby property. However, when I try to use it within another lambda getReferrerName like this: " who likes to ${getHobby}", it doesn't work as expected.

Why can't I pass getHobby inside getReferrerName to handle the null case for the referrer's hobby?

I understand there are simpler ways to solve this with if/else statements, but I'm learning lambdas and would like to find a solution using them.

If the referrer's hobby is null, the output should print .

Desired Output:

Name: Amanda
Age: 33
 . Doesn't have a referrer.
Name: Atiqah
Age: 28
 likes to climb Has a referrer named Amanda .

If the referrer's hobby is not null, the output should include it:

Name: Amanda
Age: 33
 likes to run Doesn't have a referrer.
Name: Atiqah
Age: 28
 likes to climb Has a referrer named Amanda who likes to run .

Temporary Solution:

I've implemented a temporary solution by adding an if/else check within getReferrerName

val getReferrerName: (Person?) -> String = { 
    if (it != null) "Has a referrer named ${it.name} ${if(it.hobby !=null) "who likes to ${it.hobby}" else ' '} ." 
    else "Doesn't have a referrer." 
}

However, I'd prefer a more elegant solution using lambdas if possible.


Solution

  • why can't i pass getHobby inside it i.e " who likes to ${getHobby}", since it already handles null case?

    Well you can write a call to getHobby - just declare getHobby first. Then you can write ${getHobby(it.hobby)}.

    val getHobby:(String?) -> String = { if (it != null)"likes to $it" else "." }
    val getReffererName: (Person?) -> String = { if (it != null) "Has a referrer named ${it.name} who likes to ${getHobby(it.hobby)} ." else "Doesn't have a referrer." }
    

    but it won't produce the desired output. When hobby is not null, it would produce two occurrences of "likes to", e.g.

    likes to climb Has a referrer named Amanda who likes to likes to run .
    

    This would also produce two . characters when hobby is null, which if I understand correctly, is undesirable.

    At the end of the day, the logic of getHobby is simply not very reusable, because you want the hobby to be formatted in one way for the person's own hobby (. when null, and "likes to" prefix), but in another way for the referrer's hobby (empty string when null, and "who likes to" prefix). All that's in common is the "likes to" part. If you want to reuse that, you can do:

    private val hobbyDescription get() = hobby?.let { "likes to $it" }
    private val referrerDescription get() = referrer?.let {
        val hobbyDescription = it.hobbyDescription?.let { " who $it" } ?: ""
        "Has a referrer named ${it.name}$hobbyDescription ."
    }
    
    fun showProfile() {
        println("Name: ${name}\nAge: $age\n ${hobbyDescription ?: "."} ${referrerDescription ?: "Doesn't have a referrer."}" )
    }
    

    Note that I made the descriptions of the hobby and referrer private properties instead of local vals of a function type because this makes more sense to me. If you prefer your own style, you can do something like this:

    fun showProfile() {
        val getHobby:(String?) -> String? = {
            if (it != null) "likes to $it" else null
        }
        val getReferrerName: (Person?) -> String = {
            val hobbyDescription = getHobby(it?.hobby)?.let { " who $it" } ?: ""
            if (it != null) "Has a referrer named ${it.name}$hobbyDescription ."
            else "Doesn't have a referrer."
        }
        println("Name: ${name}\nAge: $age\n ${getHobby(hobby) ?: "."} ${getReferrerName(referrer)}" )
    }