Null check with ?. vs ?: fails in kotlin - kotlin

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 ...

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
}

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!

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.

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")
}

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.

Related

How do I get rid of Boolean method is always inverted warning?

fun findError(puzzle: Array<IntArray>): Boolean {
for (z in 0..8) {
val blockNums = mutableListOf<Int>()
val xNums = mutableListOf<Int>()
val yNums = mutableListOf<Int>()
for (index in 0..8) {
xNums.add(puzzle[z][index])
yNums.add(puzzle[index][z])
blockNums.add(puzzle[blocks.xy[z + 1][index]][blocks.xy[z][index]])
if (blockNums.count() != blockNums.toSet().count() ||
yNums.count() != yNums.toSet().count() ||
xNums.count() != xNums.toSet().count()) return false
}
}
return true
}
This function works as desired but the Intellij IDE gives this warning. "Boolean method 'findError' is always inverted". I kind of understand what it means and I know I could suppress it.
I can't figure out how to rewrite the code block to satisfy the error and not change the functionality. Should I just suppress it or is there a more proper way to express this? I'm a beginner that is learning.
public final data class Blocks public constructor(blockNums: kotlin.collections.MutableList<kotlin.Int>, blockNumsFinal: kotlin.collections.MutableSet<kotlin.Int>, xy: kotlin.collections.List<kotlin.collections.List<kotlin.Int>>) {
public final val blockNums: kotlin.collections.MutableList<kotlin.Int> /* compiled code */
public final val blockNumsFinal: kotlin.collections.MutableSet<kotlin.Int> /* compiled code */
public final var xy: kotlin.collections.List<kotlin.collections.List<kotlin.Int>> /* compiled code */
public final operator fun component1(): kotlin.collections.MutableList<kotlin.Int> { /* compiled code */ }
public final operator fun component2(): kotlin.collections.MutableSet<kotlin.Int> { /* compiled code */ }
public final operator fun component3(): kotlin.collections.List<kotlin.collections.List<kotlin.Int>> { /* compiled code */ }
}
I found a way to rewrite the function not using the '!' symbol. This should satisfy the intention of the warning to use positives instead of negatives. It still gives the warning. I think Bas Leijdekkers comment about the inspection may be correct.
fun findError(puzzle: Array<IntArray>): Boolean {
val blockNums = mutableListOf<Int>()
val xNums = mutableListOf<Int>()
val yNums = mutableListOf<Int>()
var counts = 0
for (z in 0..8) {
blockNums.clear()
xNums.clear()
yNums.clear()
for (index in 0..8) {
xNums.add(puzzle[z][index])
yNums.add(puzzle[index][z])
blockNums.add(puzzle[blocks.xy[z + 1][index]][blocks.xy[z][index]])
if (blockNums.count() == blockNums.toSet().count() &&
yNums.count() == yNums.toSet().count() &&
xNums.count() == xNums.toSet().count()) {
counts++
}
}
}
return counts == 81
}
Which line is the IntelliJ warning you about, is it this statement?
if (blockNums.count() != blockNums.toSet().count() ||
yNums.count() != yNums.toSet().count() ||
xNums.count() != xNums.toSet().count()) return false
if so, it is because this is complex and likely difficult for someone other than you to understand. Here's two ideas about how you might make reduce the complexity:
(1)
if (blockNums.size != blockNums.toSet().size) return false
if (yNums.size != yNums.toSet().size) return false
if (xNums.size != xNums.toSet().size) return false
or (2)
val blocksDiffer = (blockNums.size != blockNums.toSet().size)
val yDiffer = (yNums.size != yNums.toSet().size)
val xDiffer = (xNums.size != xNums.toSet().size)
if(blocksDiffer || yDiffer || xDiffer) return false
(There is a small performance penalty with (2) since the program has to compute all 3 evaluations.
I also changed count() to size which are equivalent)
I believe this is IntelliJ trying to warn you that a boolean method result is always inverted after calling. In other words, you only ever use !findError() in your code.
This is an indication that the code could be made more readable by using positive language. JetBrain's justification for this appears to be based on Robert Martin's book Clean Code:
“Negatives are just a bit harder to understand than positives. So, when possible, conditionals should be expressed as positives.”
You can use Refactor -> Invert Boolean... to perform this automatically.

How to get object of Maximum value from LiveData?

I have liveData of market data. I want one market data object which have highest 'volume'. Here, volume is string value("277927.5793846733451135"), it could be null also.
I am using below code to achieve this. but, its not working.
viewModel.marketlist.observe(this as LifecycleOwner, { marketdata ->
val marketData = marketdata.getOrNull()
if(marketData !=null) {
val mData: MarketData? = marketData.marketData?.maxByOrNull { checkNotNull(it.volume) }
if (mData != null) {
binding.textViewPrice.text = mData.price
}
}
else {
//TODO
}
})
Any help would be appreciated!
You should be able to do something like this:
viewModel.marketList.observe(viewLifecycleOwner) { marketData ->
val maxData = marketData.getOrNull()?.marketData?.let { dataValues ->
dataValues.maxByOrNull { it.volume?.toDoubleOrNull() ?: -1.0 }
}
if (maxData != null) {
binding.textViewPrice.text = maxData.price
}
}
I cleaned up the observe call a bit, then I'm checking if marketData.getOrNull().marketData is null right away with my let { ... } block.
If you do have marketData (the inner one), it'll then safely call maxByOrNull { it.volume }.

Nested Loop select the minimum defined value asp.net

I have a list of states, which are defined to be ordered by min to max. the sequence is the following:
Cancelled - complete - draft - reservation - reserved - ordered - confirmed
So the cancelled is the minimum state, and confirmed is the maximum state. I may have different instances with different states, so I use a for-each loop to run through all states, and select the minimum state present in the loop.
That is: if in a list I have states [complete, reserved, draft, ordered] I need to check all the values and select complete -as it appears to be the minimum state. OR
if I have [reserved, confirmed, ordered, draft, cancelled, confirmed, confirmed] I need to select the cancelled value, as it appears to be the minimum.
I am doing the following check, but it does not seem to be working:
string globstatus = " ";
foreach (var currentstatus in list)
{
if (currentstatus == "cancelled")
{
globstatus = "cancelled";
}
else
{
if (globstatus == "cancelled")
{
return globstatus;
}
else
{
if (currentstatus == "complete")
{
globstatus = "complete";
}
else
{
if (globstatus == "complete")
{
return globstatus;
}
else
{
if (currentstatus == "draft")
{
globstatus = "draft";
}
else
{
if (globstatus == "reservation")
{
return globstatus;
}
else
{
if (currentstatus == "reserved")
{
globstatus = "reserved";
}
else
{
if (globstatus == "ordered")
{
return globstatus;
}
else
{
if (currentstatus == "confirmed")
{
globstatus = "confirmed";
}
else
{
return currentstatus;
}
}
}
}
}
}
}
}
}
}
return globstatus;
What can be the best solution to achieve the desired behavior?
I find a rule of thumb helpful that if I need more than three levels of braces, I need to rethink my code. It's hard to follow, easy to make mistakes, and a nightmare to debug. I suggest that applies here - trying to follow the flow of what all those nested if..else statements is extremely difficult.
Using Enum
My preferred solution is to achieve this using an Enum, e.g.:
var list = new List<Status>
{
Status.Complete,
Status.Draft,
Status.Draft,
Status.Confirmed
};
var minStatus = (Status)list.Select(l => (int)l).Min();
// minStatus = Status.Complete
public enum Status
{
Cancelled,
Complete,
Draft,
Reservation,
Reserved,
Ordered,
Confirmed
}
How it works: by default Enums give each value a zero-based integer, i.e. Cancelled = 0, Complete = 1 and so on. You can override this with your own values if you wish (e.g. 1/2/4/8/16 if you want to combine multiple values).
I recommend using Enum types for things like this, rather than strings. It helps avoid typos, gives someone else looking at your code a clear understanding of how your program works and its flow, and represents hierarchy in a way in which simple strings don't. (For example - does 'complete' come before or after 'draft'? Without context, I imagine most people would say after, but in this case it comes before - that is much more obvious when using an Enum.)
Parse strings to Enum
However if the statuses have to be strings, you could parse them into an enum like so:
var stringList = new List<string>
{
"complete",
"draft",
"draft",
"confirmed",
"this will be ignored"
};
var statusList = new List<int>();
foreach (var str in stringList)
{
if(Enum.TryParse(typeof(Status), str, ignoreCase: true, out object? parsed) && parsed is Status status)
{
statusList.Add((int)status);
}
}
var minStatus = (Status)statusList.Min();
// minStatus = Status.Complete
However, if it's possible to refactor your code to use the Enum in the first place, that would be a better solution, and much quicker as parsing strings has an overhead that would be good to avoid.

Is there a way to merge filter and map into single operation in Kotlin?

The below code will look for "=" and then split them. If there's no "=", filter them away first
myPairStr.asSequence()
.filter { it.contains("=") }
.map { it.split("=") }
However seeing that we have both
.filter { it.contains("=") }
.map { it.split("=") }
Wonder if there's a single operation that could combine the operation instead of doing it separately?
You can use mapNotNull instead of map.
myPairStr.asSequence().mapNotNull { it.split("=").takeIf { it.size >= 2 } }
The takeIf function will return null if the size of the list returned by split method is 1 i.e. if = is not present in the string. And mapNotNull will take only non null values and put them in the list(which is finally returned).
In your case, this solution will work. In other scenarios, the implementation(to merge filter & map) may be different.
I see your point and under the hood split is also doing an indexOf-check to get the appropriate parts.
I do not know of any such function supporting both operations in a single one, even though such a function would basically just be similar to what we have already for the private fun split-implementation.
So if you really want both in one step (and require that functionality more often), you may want to implement your own splitOrNull-function, basically copying the current (private) split-implementation and adapting mainly 3 parts of it (the return type List<String>?, a condition if indexOf delivers a -1, we just return null; and some default values to make it easily usable (ignoreCase=false, limit=0); marked the changes with // added or // changed):
fun CharSequence.splitOrNull(delimiter: String, ignoreCase: Boolean = false, limit: Int = 0): List<String>? { // changed
require(limit >= 0, { "Limit must be non-negative, but was $limit." })
var currentOffset = 0
var nextIndex = indexOf(delimiter, currentOffset, ignoreCase)
if (nextIndex == -1 || limit == 1) {
if (currentOffset == 0 && nextIndex == -1) // added
return null // added
return listOf(this.toString())
}
val isLimited = limit > 0
val result = ArrayList<String>(if (isLimited) limit.coerceAtMost(10) else 10)
do {
result.add(substring(currentOffset, nextIndex))
currentOffset = nextIndex + delimiter.length
// Do not search for next occurrence if we're reaching limit
if (isLimited && result.size == limit - 1) break
nextIndex = indexOf(delimiter, currentOffset, ignoreCase)
} while (nextIndex != -1)
result.add(substring(currentOffset, length))
return result
}
Having such a function in place you can then summarize both, the contains/indexOf and the split, into one call:
myPairStr.asSequence()
.mapNotNull {
it.splitOrNull("=") // or: it.splitOrNull("=", limit = 2)
}
Otherwise your current approach is already good enough. A variation of it would just be to check the size of the split after splitting it (basically removing the need to write contains('=') and just checking the expected size, e.g.:
myPairStr.asSequence()
.map { it.split('=') }
.filter { it.size > 1 }
If you want to split a $key=$value-formats, where value actually could contain additional =, you may want to use the following instead:
myPairStr.asSequence()
.map { it.split('=', limit = 2) }
.filter { it.size > 1 }
// .associate { (key, value) -> key to value }

How to switch cases with ( - ) and ( + ) keys presses?

I am working with AutoHotKey. I know I have tagged C also, I think someone with enough C programming knowledge can also help here.
Code below is working for me.
It will read two keyboard input from user and based on what user pressed it will run code for that case.
1::
Input Key, L1
if Key=1
{
;your code
}
if Key=2
{
;your code
}
2::
Input Key, L1
if Key=1
{
;your code
}
if Key=2
{
;your code
}
I would like to know if I can add a loop or something if user presses + or - key it will go do one case at a time,
for example if user presses + for first time it will do
1 1 if user presses + again it will do
1 2 if user presses - it will do
1 1
and so on.
I am not sure if this is do able or not.
I am new to programming. please help :)
You can use global variables. A global variable can be accessed anywhere in the program, unlike a normal variable which exists only inside the function.
Example:
#NoEnv
#Persistent
SetBatchLines, -1
global myVar = 0
h::
myVar := myVar + 1
execute()
return
g::
myVar := myVar - 1
execute()
return
execute()
{
if(myVar == 1)
{
;do stuff
tooltip, myVar: %myVar%
}
else if (myVar == 2)
{
;do stuff
tooltip, myVar: %myVar%
}
else if (myVar == 3)
{
;do stuff
tooltip, myVar: %myVar%
}
else if (myVar == 4)
{
;do stuff
tooltip, myVar: %myVar%
}
else if (myVar == 5)
{
;do stuff
tooltip, myVar: %myVar%
}
else
{
; nothing
tooltip,
}
return
}
I hope this is what you were asking, i wasn't quite sure from the question.
; Some of this is what's called Pseudo code. (not sure if you're familiar). It gives you needs to be turned into actual code...
; Written for AHK...
CurrentNumber = 1
(plus key)::
CurrentNumber += 1
send %CurrentNumber%
return
(minus key)::
CurrentNumber -= 1
send %CurrentNumber%
return
; Not sure if this is what you were looking for or not.. if you want a loop it will be different.
; either way, good luck to you, i'm out..
+::
keywait, +, u
{
If var =
var = 11
Else
var++
}
Return
-::
keywait, -, u
{
If var =
var = 11
Else
var--
}
Return
"var" should have same name with the variable, which has two or one digit number, in your code.
You may use this too
NumpadAdd::
keywait, NumpadAdd, u
{
If var =
var = 11
Else
var++
}
Return
NumpadSub::
keywait, NumpadSub, u
{
If var =
var = 11
Else
var--
}
Return