7

I wonder what is the best way to handle such scenario

class Person(var name:String? = null, var age:Int? = null){
    fun test(){
        if(name != null && age != null)
            doSth(name, age) //smart cast imposible
    }

    fun doSth (someValue:String, someValue2:Int){

    }
}

What is the simplest way to call doSth method and making sure that name and age are nt null?

I am looking for something simple as with one variable scenario where I would simply use let

name?.let{ doSth(it) } 
Igor
  • 2,039
  • 23
  • 27

7 Answers7

10

You can nest let as much as you like so:

fun test(){
    name?.let { name ->
        age?.let { age ->
            doSth(name, age) //smart cast imposible    
        }
    }
}

Another approach, that might be easier to follow, is to use local variables:

fun test(){
    val name = name
    val age = age
    if(name != null && age != null){
        doSth(name, age)
    }
}

Last but not least, consider changing Person to be immutable like so:

data class Person(val name:String? = null, val age:Int? = null){
    fun test(){
        if(name != null && age != null){
            doSth(name, age)
        }
    }
    ...
}
miensol
  • 39,733
  • 7
  • 116
  • 112
  • 2
    Thanx for the answear. All the proposed solutions still feel like 'a lot of code to do simple job', at least comparing to let and single variable ;-/ – Igor Aug 31 '16 at 13:28
  • 3
    @Panel I sort of like that it requires writing _a lot of code_ as you've put it. Although I must say I sort of like that fact since it encourages one to use immutable types and read only properties. – miensol Aug 31 '16 at 13:37
  • 1
    This is very good point thx. although I am not sure if it's possible to applay it every where ex. data comminig from server – Igor Sep 01 '16 at 12:22
3

For the cast to be possible you have to make a local copy of the value somehow. In Kotlin this is best done explicitly:

val name = name
val age = age
if(name != null && age != null){
    doSth(name, age)
}

The let function hides this behind an abstraction layer, which is not the best IMHO.

voddan
  • 31,956
  • 8
  • 77
  • 87
2

There's a nice, little lib that allows for writing let-like code with multiple variables. It's open-source and you can find it on GitHub, it's called Unwrap

Example based on readme:

unwrap(_a, _b, _c) { a, b, c ->
    println("$a, $b$c") // all variables are not-null
}

All unwrap(...) methods are marked inline so there should be no overhead with using them.

By the way, this lib also allows to handle situation when there are some null variables (the nah() method).

rafal
  • 3,120
  • 1
  • 17
  • 12
0

If you want to take it a little "extreme" you could define an extension function on Pair<String?,Int?> that hides the logic for you:

fun Pair<String?,Int?>.test(block: (String, Int) -> Unit) {
    if(first != null && second != null) {
         block(first, second)
    }
}

then, calling it will be a little more concise

(name to age).test { n, a ->
   println("name: $n age: $a")
}

However, it won't really help you (since you could as well define this as a function inside the Person class itself), unless you need this kind of functionality really often throughout the whole project. Like I said, it seems overkill.

edit you could actually make it (a little) more useful, by going fully generic:

fun <T,R> Pair<T?,R?>.ifBothNotNull(block: (T, R) -> Unit) {
    if(first != null && second != null){
         block(first, second)
    }
}
Lovis
  • 9,513
  • 5
  • 31
  • 47
0

In addition to miensol's answer there are various ways to copy property values into function variables to enable smart cast. e.g.:

  1. Intermediary function:

    class Person(var name: String? = null, var age: Int? = null) {
        fun test() = test(name, age)
        private fun test(name: String?, age: Int?) {
            if (name != null && age != null)
                doSth(name, age) //smart cast possible
        }
    
        fun doSth(someValue: String, someValue2: Int) {
    
        }
    }
    
  2. Anonymous function:

    class Person(var name: String? = null, var age: Int? = null) {
        fun test() = (fun(name: String?, age: Int?) {
            if (name != null && age != null)
                doSth(name, age) //smart cast possible
        })(name, age)
    
        fun doSth(someValue: String, someValue2: Int) {
    
        }
    }
    
  3. Default arguments:

    class Person(var name: String? = null, var age: Int? = null) {
        fun test(name: String? = this.name, age: Int? = this.age) {
            if (name != null && age != null)
                doSth(name, age) //smart cast possible
        }
    
        fun doSth(someValue: String, someValue2: Int) {
    
        }
    }
    
Community
  • 1
  • 1
mfulton26
  • 29,956
  • 6
  • 64
  • 88
0

It is possible to define an inline method that allows you to take N parameters in order to avoid nesting lets (I'm basing my answer on this).

inline fun <T1: Any, T2: Any, R: Any> safeLet(p1: T1?, p2: T2?, block: (T1, T2)->R?): R? {
    return if (p1 != null && p2 != null) block(p1, p2) else null
}

Then

fun test() {
    safeLet(name, age, {name, age -> 
        doSth(name, age) //smart cast
    });
}
EpicPandaForce
  • 79,669
  • 27
  • 256
  • 428
0

I was having the problem while assigning text to textview with same problem description. All I did was putting double exclamation mark after the name of my textview. For example:

var name:TextView?=null
name = findViewById(R.id.id_name)
name!!.text = "Your text"
Ashutosh Shukla
  • 358
  • 5
  • 14