Just curious: In Kotlin, I would love to get some val that can be initialized by lazy, but with a parameter. That's because I need something that's created very late in order to initialize it.
Specifically, I wish I had:
private lateinit val controlObj:SomeView
or:
private val controlObj:SomeView by lazy { view:View->view.findViewById(...)}
and then:
override fun onCreateView(....) {
val view = inflate(....)
controlObj = view.findViewById(...)
or in the 2nd case controlObj.initWith(view)
or something like that:
return view
I cannot use by lazy
because by lazy
won't accept external parameters to be used when initialising. In this example - the containing view
.
Of course I have lateinit var
but it would be nice if I could make sure it becomes read only after setting and I could do it in one line.
Is there a pretty clean way to create a read only variable that initializes only once but only when some other variables are born? Any init once
keyword? That after init the compiler knows it's immutable?
I am aware of the potential concurrency issues here but if I dare to access it before init, I surely deserve to be thrown.
You cannot use val for lateinit variable as it will be initialized later on.
NOTE: To use a lateinit variable, your variable should use var and NOT val . Lateinit is allowed for non-primitive data types only and the variable can't be of null type. Also, lateinit variable can be declared either inside the class or it can be a top-level property.
In order to create a "lateInit" variable, we just need to add the keyword "lateInit" as an access modifier of that variable. Following are a set of conditions that need to be followed in order to use "lateInit" in Kotlin. Use "lateInit" with a mutable variable. That means, we need to use "var" keyword with "lateInit".
AndroidMobile DevelopmentApps/ApplicationsKotlin. In Kotlin, we can declare a variable using two different keywords: one is var and the other one is val.
You can implement own delegate like this:
class InitOnceProperty<T> : ReadWriteProperty<Any, T> {
private object EMPTY
private var value: Any? = EMPTY
override fun getValue(thisRef: Any, property: KProperty<*>): T {
if (value == EMPTY) {
throw IllegalStateException("Value isn't initialized")
} else {
return value as T
}
}
override fun setValue(thisRef: Any, property: KProperty<*>, value: T) {
if (this.value != EMPTY) {
throw IllegalStateException("Value is initialized")
}
this.value = value
}
}
After that you can use it as following:
inline fun <reified T> initOnce(): ReadWriteProperty<Any, T> = InitOnceProperty()
class Test {
var property: String by initOnce()
fun readValueFailure() {
val data = property //Value isn't initialized, exception is thrown
}
fun writeValueTwice() {
property = "Test1"
property = "Test2" //Exception is thrown, value already initalized
}
fun readWriteCorrect() {
property = "Test"
val data1 = property
val data2 = property //Exception isn't thrown, everything is correct
}
}
In case when you try to access value before it is initialized you will get exception as well as when you try to reassign new value.
In this solution you implement a custom delegate and it becomes a separate property on your class. The delegate has a var
inside, but the controlObj
property has the guarantees you want.
class X {
private val initOnce = InitOnce<View>()
private val controlObj: View by initOnce
fun readWithoutInit() {
println(controlObj)
}
fun readWithInit() {
initOnce.initWith(createView())
println(controlObj)
}
fun doubleInit() {
initOnce.initWith(createView())
initOnce.initWith(createView())
println(controlObj)
}
}
fun createView(): View = TODO()
class InitOnce<T : Any> {
private var value: T? = null
fun initWith(value: T) {
if (this.value != null) {
throw IllegalStateException("Already initialized")
}
this.value = value
}
operator fun getValue(thisRef: Any?, property: KProperty<*>): T =
value ?: throw IllegalStateException("Not initialized")
}
BTW if you need thread safety, the solution is just slightly different:
class InitOnceThreadSafe<T : Any> {
private val viewRef = AtomicReference<T>()
fun initWith(value: T) {
if (!viewRef.compareAndSet(null, value)) {
throw IllegalStateException("Already initialized")
}
}
operator fun getValue(thisRef: Any?, property: KProperty<*>): T =
viewRef.get() ?: throw IllegalStateException("Not initialized")
}
You can use lazy
. For example with TextView
val text by lazy<TextView?>{view?.findViewById(R.id.text_view)}
where view
is getView()
.
And after onCreateView()
you can use text
as read only variable
You can implement own delegate like this:
class LateInitVal {
private val map: MutableMap<String, Any> = mutableMapOf()
fun initValue(property: KProperty<*>, value: Any) {
if (map.containsKey(property.name)) throw IllegalStateException("Value is initialized")
map[property.name] = value
}
fun <T> delegate(): ReadOnlyProperty<Any, T> = MyDelegate()
private inner class MyDelegate<T> : ReadOnlyProperty<Any, T> {
override fun getValue(thisRef: Any, property: KProperty<*>): T {
val any = map[property.name]
return any as? T ?: throw IllegalStateException("Value isn't initialized")
}
}
}
After that you can use it as following:
class LateInitValTest {
@Test
fun testLateInit() {
val myClass = MyClass()
myClass.init("hello", 100)
assertEquals("hello", myClass.text)
assertEquals(100, myClass.num)
}
}
class MyClass {
private val lateInitVal = LateInitVal()
val text: String by lateInitVal.delegate<String>()
val num: Int by lateInitVal.delegate<Int>()
fun init(argStr: String, argNum: Int) {
(::text) init argStr
(::num) init argNum
}
private infix fun KProperty<*>.init(value: Any) {
lateInitVal.initValue(this, value)
}
}
In case when you try to access value before it initialized you will get exception as well as when you try to reassign new value.
Safe delegation, Synchronized and helpfull messages
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
interface InitOnce<T> : ReadWriteProperty<Any?, T> {
val isInitialized: Boolean
val value: T
}
class SynchronizedInitOnceImpl<T> : InitOnce<T> {
object UNINITIALIZED_VALUE
private var name: String? = null
@Volatile
private var _value: Any? = UNINITIALIZED_VALUE
override val value: T
@Suppress("UNCHECKED_CAST")
get() {
val _v = synchronized(this) { _value }
if (_v !== UNINITIALIZED_VALUE) return _v as T
else error("'$name' not initialized yet")
}
override val isInitialized: Boolean
get() = _value !== UNINITIALIZED_VALUE
override operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
if(name == null) name = property.name
return value
}
override operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
synchronized(this) {
val _v = _value
if (_v !== UNINITIALIZED_VALUE) error("'${property.name}' already initialized")
else _value = value
}
}
}
fun <T> initOnce(): InitOnce<T> = SynchronizedInitOnceImpl()
Usage
var hello: String by initOnce()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With