https://kotlinlang.org logo
Title
f

farmerbb

02/01/2023, 4:55 PM
Lesson learned: don't use an
init
block inside of an
abstract class
🤦‍♂️
l

Loney Chou

02/01/2023, 4:59 PM
Can you elaborate?
f

farmerbb

02/01/2023, 4:59 PM
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

Pablichjenkov

02/01/2023, 5:11 PM
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

farmerbb

02/01/2023, 5:15 PM
That's a fair point. The IDE doesn't warn me about it though, at least in a class that looks something like this:
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.
p

Pablichjenkov

02/01/2023, 5:29 PM
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.
f

farmerbb

02/01/2023, 5:30 PM
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!
g

Gleb Minaev

02/01/2023, 6:57 PM
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:
fun exec(block: () -> Unit) { block() }

abstract class SomeAbstractClass {

    protected abstract val someVal: String

    init {
        exec {
            someVal.length
        }
    }
}
p

Pablichjenkov

02/01/2023, 7:05 PM
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

Ruckus

02/01/2023, 7:19 PM
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
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

Pablichjenkov

02/01/2023, 7:40 PM
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 🤷🏻