Is there a generally accepted pattern to deal with...
# announcements
t
Is there a generally accepted pattern to deal with variables that never change after being set, but they arn't known at object construction time? lateinit helps w/ the late-part, but not the val part. It'd be nice if the compiler could help if anyone is trying to change it.
c
I think a
lazy { }
property is what you’re looking for https://kotlinlang.org/docs/reference/delegated-properties.html#lazy
n
I feel like lazy is different from what's described here; lazy is set on the first get, whenever that happens. This is set sometime after construction, but independently of when get is called
@TwoClocks i can see that this answer might be annoying but if at all possible the best thing to do is to refactor the code so that the variable simply doesn't need to be created until its value is known
sometimes this refactoring may be too painful to do, but I find in the majority of cases it's doable and preferable (especially in new code)
In many cases, the reason people want this is because they want to create a class that contains a Foo at some point in time, but they can't yet create the Foo when they want to create the rest of the class. The solution then is typically to break that class into two classes; one that doesn't have the Foo and one that does. This solves the issue and also gives you more type safety (functions take whichever class is appropriate; this guarantees at compile time that no functions that need to access the Foo member are receiving an object that doesn't have it initialized)
i
@TwoClocks Temporal dependency is not pretty; I think a good answer depends on how the value is set~ (If not the constructor, who? if not at construction time, when? Is it at all possible that the property can be accessed before it is set? and if so, what happens?)
r
I normally just define a val neverChange get() { return “dowork” }
i
that’s similar to what lazy does, isn’t it, @Ryan?
r
yes, sometimes i can’t use lazy on android though
👍 1
t
@Nir I have it split out now, and it isn't pretty, which is why I'm looking at other options
n
What's the reason you think it's not pretty? I think having two separate types is the most correct thing; any other solution just fundamentally compromises type safety
t
@Irving Rivas it's set by a msg sent over the network (guaranteed to be the first msg you get), and it doesn't change for the rest of the day.
@Nir It's a a lot of duplicated code
n
where's the duplication coming from? I'm basically just picturing composition, you have a class that is the other stuff without the message, and then a class that has the message + other stuff as members.
i
@TwoClocks it seems from your description that
lazy
may do the job, if it’s not something that gets used on the UI thread and you can just block in the lambda.
a
Is that value set from the network internally in the class? If so you could have a
private lateinit var
and expose it outwardly with a public val getter?
Copy code
class Example1 {
    private lateinit var _value: Any
    val value get() = _value
}
If the value is set the first time from outside the class I don't see how the compiler could know which assignment is first, in order to disallow others. If what you are merely concerned with the value changing after the fact you could make later assignments no-op, either with internal logic, or with a delegate:
Copy code
fun <T> setOnce(
        accessBeforeSet: () -> T = throw IllegalStateException("Value not yet set"),
        setAgain: (previousValue: T) -> T = throw IllegalStateException("Value has already been set")
) = object : ReadWriteProperty<Any?, T> {
    @Volatile private var value: T? = null

    override operator fun getValue(thisRef: Any?, property: KProperty<*>) = value ?: accessBeforeSet()

    override operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        this.value = this.value?.let(setAgain) ?: value
    }
}

class Example2 {
    private var value by setOnce<Any>(setAgain = {
        it // USE PREVIOUS VALUE
    })
}
Regardless, relying on network logic for a temporal order of initialization without making the uninitialized state outwardly visible strikes me as a generally bad idea. I would in that case follow @Nir's advice and refactor the code to avoid the situation even if it is more code. I find sealed classes are often a good fit if there are a finite number of states (e.g. uninitialized or initialized with value).
🔥 1