Lesson learned: don't use an `init` block inside o...
# random
f
Lesson learned: don't use an
init
block inside of an
abstract class
🤦‍♂️
l
Can you elaborate?
f
Deployed a release that tested just fine on my end, then started getting a bunch of crash alerts for
Attempt to invoke interface method <name> on a null object reference
that I couldn't reproduce
After racking my brain as to what could be causing the issue it finally clicked for me. then saw this reddit post that confirmed it https://www.reddit.com/r/androiddev/comments/803dq3/kotlin_booby_trap_never_use_init_in_openabstract/
p
Is not that is terrible using an init block in an abstract class, you just should not touch open/abstract properties/functions that are expected to be override in the derived classes. I believe the IDE warns you anyways.
f
That's a fair point. The IDE doesn't warn me about it though, at least in a class that looks something like this:
Copy code
abstract class SomeAbstractClass(
    scope: CoroutineScope
) {

    protected abstract val someVal: SomeType

    init {
        scope.launch {
            someVal.someFunction()
        }
    }
}
If I call
someVal.someFunction()
outside of the coroutine scope then the IDE warns me with
Accessing non-final property <name> in constructor
but no such warning while inside the coroutine scope.
👍 1
p
Ah I see, interesting. The no warning make sense because the launch block won't execute synchronously. However, if you use a dispatcher that executes the launch block immediately in the executing thread, it will crash. It would be good to file for an enhancement in the syntax highlighting area.
👍 1
f
Yeah my guess is we were just running into race conditions on certain devices where the coroutine would execute either before or after the abstract val in the subclass was initialized. I'll look into filing an enhancement. Thanks!
1
g
Looks like there is no warning when abstract function or value is used not in constructor scope exactly. For example, here it does not warn about anything too:
Copy code
fun exec(block: () -> Unit) { block() }

abstract class SomeAbstractClass {

    protected abstract val someVal: String

    init {
        exec {
            someVal.length
        }
    }
}
p
It seems like so, the warning should trigger whenever a property name is found within init{}, regardless of scope. Perhaps giving the option to disable if the developer is aware of what is doing.
r
the warning should trigger whenever a property name is found within init{}, regardless of scope
I would say no. Counter use case I run into commonly is something along the lines of
Copy code
abstract class Thing<T> : Observable<T> {
    abstract val value: Stuff

    init {
        addListener { doSomething(it, value) }
    }
}
Which is perfectly valid, as the lambda will not be executed till later.
p
Well, I suspect there is a lot of code looking like that. In such a case, it is hard to determine whether or not to warn. Then, another alternative is leave it up to the official documentation. Red letters as soon as you open the inheritance docs page 🤷🏻