1

I am studying Android and I am also studying Kotlin.

While writing Android code, I was curious about using it in a let function.

MainActivity.kt

class MainActivity : AppCompatActivity() {
    private var curFrag: Fragment? = null
    curFrag = fm.primaryNavigationFragment

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

//      curFrag?.let { transaction.hide(curFrag) } // error.
        curFrag?.let { transaction.hide(it) }

    }
}

ERROR

Smart cast to 'Fragment' is impossible, because 'curFrag' is a mutable property that could have been changed by this time

In the lambda expression of let(), T is curFrag and the type is Fragment? is.

And T(curFrag) can be replaced by it.

But the moment I used curFrag instead of it, the IDE displayed an error message.

Later, when I checked the type of it, it was Fragment? It was not a Fragment type.

Honestly, I don't understand well.

I don't know why it is automatically smart cast and should only be used for immutable variables.

ybybyb
  • 1,385
  • 1
  • 12
  • 33
  • 1
    "Later, when I checked the type of it, it was Fragment? It was not a Fragment type." -- you are using a safe call form of `let()` (`?.let { }`). `it`, inside the lambda expression passed to `let()`, will not be a nullable type. – CommonsWare May 21 '21 at 20:37
  • 1
    Note `it` is simply the implicit name of the parameter in a single-parameter lambda. But you can give an explicit name: `curFrag?.let { foo -> transaction.hide(foo) }`. Maybe seeing it that way can help you understand what's happening. – Slaw May 21 '21 at 20:40
  • 1
    @Slaw for general code readability I prefer explicit naming as your example - side note you could also do a method reference: `curFrag?.let(transaction::hide)` – Mark May 21 '21 at 20:44

2 Answers2

2

Kotlin is a null safe language, it tries to eliminate every possible null references from the code. You can perform a nullability check on the variable and then can use it like this

if(curfrag != null) { transaction.hide(curFrag)

This too will only work if variable curfrag is immutable (that means a local variable which is not modified between the check and the usage or a member val which has a backing field and is not overridable), because otherwise it might happen that curfrag changes to null after the check from some other thread.

But Safe calls ?. with let always gives us non nullable result, what Safe calls operator ?. does is, it only performs any operation following it, only if the variable is not-null otherwise it returns null.

It works with all mutable types or member var, It check for the null once and then provides the result. If value is non null it performs the defined operation otherwise skips it. it refers to the copy of that non-null value.

So when you do this

curFrag?.let { transaction.hide(curFrag) }

curFrag can be null as you are directly passing a nullable value.

But in this case

curFrag?.let { transaction.hide(it) }

it only passes value if it's a non-null value.

Praveen
  • 3,186
  • 2
  • 8
  • 23
  • 2
    "But let always gives us non nullable result" - this is actually wrong. The fact that you get something non-nullable is because of the safe-call `?.`, not the `let`. If you use `myVar.let()` on a nullable variable, you'll get a nullable `it` inside the block. – Joffrey May 21 '21 at 23:09
  • 1
    I think the most important fact and the best explanation is that `let()` **copies** the value. If another thread modify the `curFrag` property, it could become null, but `it` will still point at the old value which was verified to be not null. – broot May 21 '21 at 23:44
  • @Joffrey You mean `let()` doesn't always provide a `non-null type`, it can also provide a null type, A `non-null` type is determined to be `non-null` by `safe-call`, so `let()` returns a `non-null` type? – ybybyb May 22 '21 at 19:21
  • However, even if it is a `mutable type`, can't it be a `null` value if it is a `non-null type`? – ybybyb May 22 '21 at 19:22
  • 1
    @Joffery Thanks for pointing out the mistake, indeed let doesn't gives us non-nullable result, but safe calls do, I've edited my answer. – Praveen May 23 '21 at 08:45
  • 1
    @ybybyb Yes `let` doesn't gives us `non-null` values, but safe calls operator does. What happens is first safe calls operator checks for the nullable value and then it passes, only if the value is not null. After that `let` gives us a copy of that value in the `let` block – Praveen May 23 '21 at 08:49
1

The let function basically creates a new variable with the same value as whatever you called it on, so it is not really smart-casting the original property.

If you use ?.let, let isn't even called if the value was null. The safe call means the receiver let is being called on is not a nullable value to begin with because otherwise let isn't called at all. The it inside let is just a reference to what it was called on.

Effectively, though it is conceptually similar to smart-casting. There is not really a way to write equivalent Kotlin code that does what ?.let is doing because the ?. safe call is a special operator that has no expanded form.

Tenfour04
  • 83,111
  • 11
  • 94
  • 154
  • Your answer is that the `block` of `?.let()` works means `curFrag` is `not null` by `safe call`, right? So, since `curFrag` is deterministic that it is `non null`, does `let()` return a `Fragment?` type `curFrag` as a `Fragment` type? After all, is this also a `smart cast`? – ybybyb May 22 '21 at 19:30
  • 1
    `currFrag` is a property. The instant you access it, you’re no longer working with the property, but rather the value that was returned by that property at that instant in time. So it’s not quite accurate to say `currFrag` has been determined to be non null by the time `let` is safe-called. Rather, the value that was previously returned by the property accessor is what is determined to be non-null. – Tenfour04 May 22 '21 at 20:20