Reputation: 6141
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
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
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