Amiga500
Amiga500

Reputation: 6141

Checking for null variable and then making if statement on it

I am learning Kotlin. I was wondering is this the best way to solve this issue: I have a simple class:

class AlternativeCustomerTwo(
    val name: String = "Name Not Provided",
    var age: Int? = null,
    var address: String = "Address not provided"
) 

It has default parameters, and also a nullable age.

I want to include the following field:

var isApproved: Boolean = false

So now I have a class that looks like:

class AlternativeCustomerTwo(
    val name: String = "Name Not Provided",
    var age: Int? = null,
    var address: String = "Address not provided"
) {

   var isApproved: Boolean = false}

Now I want to override the default setter for the isApproved, that checks if the age is over 21, and if it is, it sets it to true. Something like this:

class AlternativeCustomerTwo(
    val name: String = "Name Not Provided",
    var age: Int? = null,
    var address: String = "Address not provided"
) { 
 var isApproved: Boolean = false
    set(value) {
            if(age >= 21) {
                field = value
            }
    }
}

The problem here is the var age. Code can't compile, and this is the error:

Error:(19, 20) Kotlin: Operator call corresponds to a dot-qualified call 'age.compareTo(21)' which is not allowed on a nullable receiver 'age'.

After some tinkering, I have implemented desired functionality like this:

class AlternativeCustomerTwo(
    val name: String = "Name Not Provided",
    var age: Int? = null,
    var address: String = "Address not provided"
) {

   var isApproved: Boolean = false
    set(value) {
       age?.let {
           if(it >= 21) {
               field = value
           }
       }
    }
}

If I call it like this:

val customer = AlternativeCustomerTwo(name = "John", age = 120)
customer.isApproved = true

Then it prints: true

Alternately

val customer = AlternativeCustomerTwo(name = "John", age = 12)
    customer.isApproved = true

The it print false My question, is this the correct approach, or I am doing some horrible Kotlin?

Upvotes: 1

Views: 812

Answers (2)

Tenfour04
Tenfour04

Reputation: 93872

?.let is a common Kotlin idiom for doing something with a variable after a null-check when you are working with a member property, because smart-casting to non-null doesn't work with member properties. But there's a more fundamental problem with your general design.

The setter for isApproved vetoes the change if the age is unknown or under 21. I can think of multiple scenarios where this is going to create hard-to-find bugs. Here is an example:

val tinyTim = AlternativeCustomerTwo("Tiny Tim", 30)
tinyTim.isApproved = true

// Oops, the age is actually 3. Let's correct it.
tinyTim.age = 3
tinyTim.isApproved = false

He will still be approved because the setter doesn't allow you to change isApproved if the age isn't over 21.

Here's another example.

val john = AlternativeCustomerTwo("John").apply {
    isApproved = true
    age = 45
}

Since we set the properties in the wrong order, he is not approved, even though we intended for him to be.

One solution is to use a custom getter instead of a custom setter:

var isApproved: Boolean = false
    get(value) = field && (age?.let { it >= 21 } ?: false)

Then you could mark someone as approved before setting their correct age, and if their age is updated, the returned value will be as well. But, for absolute clarity, I would recommend using two properties:

var isApproved = false
val isAllowed: Boolean
    get() = isApproved && (age?.let { it >= 21 } ?: false)

Then there can be no surprises about isApproved's value not matching what you explicitly set.

Upvotes: 1

mightyWOZ
mightyWOZ

Reputation: 8355

You are using two very good language features of kotlin. first is let function. it is a scope function which takes its invoking object(age in your case) and provides it as parameter(it in your case) of the lambda.

second is the safe call(?.) feature. safe call only continues if the invoking object is non null.

so effectively your setter will only be called when age is not null.

This technic is recommended by the Kotlin language designers. Following snippet is from the book Kotlin in action, chapter 6 page 144

you can use the let function, and call it via a safe call. All the let function does is turn the object on which it’s called into a parameter of the lambda. If you combine it with the safe call syntax, it effectively converts an object of a nullable type on which you call let into a non-null type

Upvotes: 0

Related Questions