https://kotlinlang.org logo
Title
p

pavankumar

02/03/2023, 4:01 PM
Please help why below code snippet throws
NullPointerException
when calling
createInstance()
method. Is there any solution to make it work
fun main() {
    val a = A.getInstance()
    println("value of ${a.num}")
}

class A {
    val num: Int = 10
    
    companion object : SingletonHolderWithoutArgs<A>(::createInstance) {
        private fun createInstance(): A {
            return A()
        }
    }
}

open class SingletonHolderWithoutArgs<out T>(private val func: () -> T) {

    @Volatile
    private var instance: T? = null

    fun getInstance(): T =
        synchronized(this) {
            instance ?: func().also { instance = it }
        }
}
s

Sam

02/03/2023, 4:10 PM
I think what’s happening here is that you’re creating a bound reference to the
A.createInstance
function inside A’s constructor, i.e. before
A
has actually been initialised. Although A has been initialised by the time you call the function, the reference was created before that happened.
I’m not sure about this though 🤔 it’s confusing
l

Luke Armitage

02/03/2023, 4:11 PM
some sort of convoluted Singleton-constructor pattern?
p

pavankumar

02/03/2023, 4:12 PM
If i put
inline
keyword before
createInstance
method then problem is solved but I don't know why
inline
keyword fixes the problem.
That’s the cause of the NPE, though it doesn’t help you fix it. Why do you need your singleton creation to be so complicated? Does something prevent you just making
A
an
object
?
The normal way to make a singleton like that in Kotlin would just be
fun main() {
    val a = A
    println("value of ${a.num}")
}

object A {
    val num: Int = 10
}
m

mkrussel

02/03/2023, 4:19 PM
Even if you cannot make it an object, you could still easily just create one in the companion, it can even use the
lazy
delegate.
p

pavankumar

02/03/2023, 4:20 PM
can you give me example
This is the class i'm trying to create.
internal class Starter private constructor(
    initializer: Initializer,
    creator: Creator,
    unlocker: Unlocker
) : Initializer by initializer,
    Creator by creator,
    Unlocker by unlocker {

    companion object :
        SingletonHolderWithoutArgs<Starter>(Starter::createInstance) {

        private inline fun createInstance(): Starter {
            val job = Components.from(AComponent::class.java).Job()
            return Starter(
                SdkDataVaultInitializerImpl(job),
                SdkDataVaultCreatorImpl(job),
                SdkDataVaultUnlockerImpl(job)
            )
        }
    }
}
m

mkrussel

02/03/2023, 4:21 PM
class A {
    val num: Int = 10

    companion object {
        val instance by lazy { A() }
    }
}

fun main() {
    val a = A.instance
    println("value of ${a.num}")
}
Seems like you are just trying to make a lazy singleton, if so just declare
Starter
as an object class. Are these singleton holders are not needed (they weren't really need in Java either)
The
createInstance
function just becomes your
init
block.
p

pavankumar

02/03/2023, 4:26 PM
Ok
If i replace
createInstance()
with
init
block then i'm making my class not easily testable right? What i mean is the
private constructor
can be
@VisibleForTesting constructor(...)
in future. So
init
block might give me trouble at that time.
In brief, I want to simplify my class construction process in production (i.e all objects needed for my class are instantiated inside the class. ) But this class should be easily testable (So want to expose a constructor that accepts all required parameters)
l

Luke Armitage

02/03/2023, 4:38 PM
hmm, sounds like you need some dependency injection - it’s generally not considered a good idea for a class to instantiate its dependencies, as (like you’ve pointed out) it’s tricky to test, and it can be inflexible.
k

Kevin Del Castillo

02/03/2023, 5:32 PM
Btw
@Volatile
is useless