Reputation: 3966
I have following statement in my code:
safeOrderResult.accomplished?.let{ safeAccomplished->
//Do something with safeAccomplished when accomplished <> null
Log.i(TAG,"bind: safeOrderResult.accomplishedId.let?{}")
}?:let{
//Do something when accomplished == null
Log.i(TAG,"bind: safeOrderResult.accomplishedId?:let{} *null*" )
}
Here my code does something strange:
On a Samsung TAB A (i think not significant) it works as expected.
On a Samsung S9 it calls both let sections.
Snippet from Logcat Samsung S9 (android 10)
2021-05-06 14:11:35.427 9069-9069/no.norva24.mslam I/ViewHolder: bind: safeOrderResult.accomplishedId = 408
2021-05-06 14:11:35.427 9069-9069/no.norva24.mslam I/ViewHolder: bind: safeOrderResult.accomplishedId.let?.{}
2021-05-06 14:11:35.427 9069-9069/no.norva24.mslam I/ViewHolder: bind: handleDate = null <- inside above let: ok
2021-05-06 14:11:35.427 9069-9069/no.norva24.mslam I/ViewHolder: bind: safeOrderResult.accomplishedId?:let{} *null*
2021-05-06 14:11:35.427 9069-9069/no.norva24.mslam I/ViewHolder: bind: flagged = false or null
TabA: android 10
2021-05-06 14:21:16.676 2468-2468/no.norva24.mslam I/ViewHolder: bind: safeOrderResult.accomplishedId = 427
2021-05-06 14:21:16.676 2468-2468/no.norva24.mslam I/ViewHolder: bind: safeOrderResult.accomplishedId.let?.{}
2021-05-06 14:21:16.678 2468-2468/no.norva24.mslam I/ViewHolder: bind: handleDate = null <-- inside above let
2021-05-06 14:21:16.685 2468-2468/no.norva24.mslam I/ViewHolder: bind: flagged = false or null
The key point is, how can a value both be null and contain a value?, or can kotlin "change" to null and kick in in the second "null" let, if value has changed in the first first let (which I didn't do)
I am Using kotlin 1.5.0
EDIT 2021.05.06 18:55 GMT+2
I am not sure, but I might have learned something here today: ;)
safeOrderResult.accomplished?.let{ safeAccomplished->
//Do something with safeAccomplished when accomplished <> null
/*Here I have preserved my value in safeAccomplished
And actually returning a value below (a Unit()) from Log.i ?*/
Log.i(TAG,"bind: safeOrderResult.accomplishedId.let?{}")
}?:let{
//Do something when accomplished == null
/* But why did the code kick in here ?
After it was inside the let above ? I thought the '?:let' was
continuing if the '?.let' didn't kick in.
*/
Log.i(TAG,"bind: safeOrderResult.accomplishedId?:let{} *null*" )
}
/*
Below is the actual code which had the trouble (the code isn't finished therefore the "preserved" `let` values isn't used)
*/
safeOrderResult.accomplishedId?.let {
listItemOrderListLinearLayoutCompatStatus.apply {
visibility = View.VISIBLE
listItemOrderListMaterialTextViewOrderStatus.text =
context.resources.getStringArray(
R.array.basic_register_accomplish_status_names)[1]
listItemOrderListMaterialTextViewDate.text =
dateStringSplitSpace(safeOrderResult.registeredDate)
Log.i(TAG, "bind: handleDate = ${safeOrderResult.handleDate}")
listItemOrderListMaterialTextViewReason.text =
if(safeOrderResult.handleDate.isNullOrEmpty())
"Still possible to update"
else
"Assignment locked on ${dateStringSplitSpace(safeOrderResult.handleDate)}"
setBackgroundColor(
ContextCompat.getColor(
itemView.context,
if(safeOrderResult.handleDate.isNullOrEmpty())
R.color.list_item_register_field_accomplished_background
else
R.color.list_item_register_field_accomplished_locked_background
)
)
}
listItemOrderListLinearLayoutCompatStatusMore?.apply {
setBackgroundColor(
ContextCompat.getColor(
itemView.context,
if(safeOrderResult.handleDate.isNullOrEmpty())
R.color.list_item_register_field_accomplished_background
else
R.color.list_item_register_field_accomplished_locked_background
)
)
}
}?:let {
safeOrderResult.passedId?.let { safePassedId->
listItemOrderListLinearLayoutCompatStatus.apply {
visibility = View.VISIBLE
listItemOrderListMaterialTextViewOrderStatus.text =
context.resources.getStringArray(
R.array.basic_register_accomplish_status_names
)[2]
listItemOrderListMaterialTextViewDate.text =
dateStringSplitSpace(safeOrderResult.registeredDate)
listItemOrderListMaterialTextViewReason.text =
safeOrderResult.passedReason
setBackgroundColor(
ContextCompat.getColor(
itemView.context,
R.color.list_item_register_field_passed_background,
)
)
}
}?:let {
listItemOrderListLinearLayoutCompatStatus.apply {
visibility = View.GONE
}
}
}
** ADDENDUM 2020.05.06 19:30 GMT+2 **
In playground I got trouble with this:
/**
* You can edit, run, and share this code.
* play.kotlinlang.org
*/
class A {
fun f() {
let { println(it) }
}
}
data class DataClass(
var value1:String?=null,
var value2:String?=null
)
fun main() {
A().f()
var myData = DataClass()
myData.value1 = "1"
myData.value1?.let{ safeValue1->
println("value1 = "+safeValue1)
}?:let{
println("value1==null !!")
}
myData.value2?.let{ safeValue2->
println("value2 = "+safeValue2)
}?:let{
println("value2==null !!")
}
}
Where it kicked on the ?:let
's above. This was ok in kotin v.1.5.0 at least...
ADDENDUM 2: 2020.05.06 19:40 GMT+2
So... dataClass.value?:let{ }
isn't allowed ? in a 'standard' kotlin scenario to check for null existence ?, but still 'valid' in AS2020.3.1.15 w/kotlin 1.5.0
...
ADDENDUM 3: 2020.05.06 19:55 GMT+2
When using another approach (omitting let
keyword in ?:let{
I got this answer to the based on the playground code above:
Here I expected also the value2 to show up with value2==null !!
but it didn`t...
Here's the playground code now:
/**
* You can edit, run, and share this code.
* play.kotlinlang.org
*/
class A {
fun f() {
let { println(it) }
}
}
data class DataClass(
var value1:String?=null,
var value2:String?=null
)
fun main() {
A().f()
var myData = DataClass()
myData.value1 = "1"
/*
myData.value1?.let{ safeValue1->
println("value1 = "+safeValue1)
}?:let{
println("value1==null !!")
}
myData.value2?.let{ safeValue2->
println("value2 = "+safeValue2)
}?:let{
println("value2==null !!")
}
*/
myData.value1?.let{ safeValue1->
println("value1 = "+safeValue1)
}
myData.value1?:{
println("value1==null !!")
}
myData.value2?.let{ safeValue2->
println("value2 = "+safeValue2)
}
myData.value2?:{
println("value2==null !!")
}
}
...still a little confused ...
Upvotes: 1
Views: 1264
Reputation: 170745
If you don't want to bind accomplished
to a variable as in @Tenfour04's answer, I'd write it as
safeOrderResult.accomplished.let {
if (it != null) {
// safeOrderResult.accomplished was not null, use it
} else {
// safeOrderResult.accomplished was null
}
}
or
safeOrderResult.accomplished.let { accomplished ->
if (accomplished != null) {
// safeOrderResult.accomplished was not null, use accomplished
} else {
// safeOrderResult.accomplished was null
}
}
Note .let
and not ?.let
. But pick on readability/convenience. I definitely wouldn't use
value?.let{ safeValue-> /*dowork*/}; value?:let{/*do null work*/}
as you suggest in another comment.
Upvotes: 1
Reputation: 3966
Thanx for many good answers above, and you all are right...
I landed on following solution for my problem, but still not quite happy though:
I use .apply
to remove some value.
overhead,
safeOrderResult.apply{
if(accomplished!=null){
//Do something with accomplished since accomplished <> null
Log.i(TAG,"bind: accomplished != null")
}else{
//Do something when accomplished == null
Log.i(TAG,"bind: accomplished == null" )
}
}
I mark accepted for @Alexey Romanov suggestion which is quite reasonable.
Upvotes: 0
Reputation: 19544
At a guess, the first one is returning null
at the end, which means the value produced by that whole expression is null
, so the stuff after the ?:
is triggered (since that's an "if the left side evaluates to null" condition).
Why that would only happen on some Samsung models - who knows, they have a history of messing with things in the Android library! I'd check exactly what's going on in the block and what it might evaluate to. You might need to return Unit
at the end, or use a function like apply
that returns the receiver instead of the result of the lambda.
This is why the if/else
is a better fit - you have a condition at the start, and you decide whether to do one thing or another, exclusively. let
produces a value, and it's often used to propagate a value down a chain, and return a result. ?:
is a final default value, for if that result turns out to be null.
It's absolutely possible to run the let
block and the code after the ?:
, and sometimes that a thing you want to do. As a construction it's often used for returning a default value. So if/else
is a little more explicit about what you're doing and how it's meant to work, and it helps avoid surprise bugs like this one!
Upvotes: 2
Reputation: 93609
The let
function can indeed change your target to null. It changes the target to whatever it returns. A lambda implicitly returns the result of its last expression. Your code above has a Log.i()
call as its last expression, so it returns Unit, so the second let
function should never run if the first one does. Is it possible you've snipped off some code at the end of your first let
lambda that could possibly return a null value?
A quick fix for the above problem would be to swap let
for also
, because also
always returns its receiver.
I think most experienced Kotlin users will advise you not to chain scope function calls like this because it makes the code hard to follow and it is easy to introduce subtle bugs. You can write a more robust version like this:
val accomplished = safeOrderResult.accomplished
if (accomplished != null) {
//Do something with smart-cast non-nullable accomplished
} else {
//Do something when accomplished == null
}
Upvotes: 2
Reputation: 15309
You can do an if-null-else with ?.let
but it's not very readable in my opinion
var s: String? = "Str"
s?.let { println("A ok") } ?: run { println("A null") }
s = null
s?.let { println("B ok") } ?: run { println("B null") }
A ok
B null
It is also possible to introduce subtle bugs like this:
var s: String? = "Str"
s?.let { println("A ok"); null } ?: run { println("A null") }
A ok
A null
This is why you should use an if-else if you both need the non-null and null case. (?.
is intended for the case where only the non-null case makes sense):
if (s == null) println("A null") else println("A ok")
if (s == null) {
println("A null")
} else {
println("A ok")
}
Upvotes: 1