Search code examples
kotlinsingleton

SingletonInstance never being set?


I get following error message:

FATAL EXCEPTION: main
    Process: com.example.myapp.debug, PID: 27111
    java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:583)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1049)
    Caused by: java.lang.reflect.InvocationTargetException
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:573)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1049)
    Caused by: com.example.ConfException: ... is NULL!
        at com.example.SingletonInstance.getInstance(SingletonInstance.kt:21)
        at com.example.ServiceClientCtxConf$Companion.getInstance(Unknown Source:2)
        at com.example.AppClientConf$Companion.setConf(AppClientConf.kt:52)
        at com.example.activities.MyActivity.onCreate(MyActivity.kt:200)
        ...

Starting from the MyActivity, I make a REST call at mentioned line 200:

AppClientConf
    .setConf() // line 200
    .doOnNext { successful ->
        //
    }
    .subscribe()

whereas this is the AppClientConf simplified is like (upper error message line):

class AppClientConf private constructor() {

    companion object {

        fun setConf(): Observable<Boolean> = 
            ServiceClientConf
                .setConf(context = ServiceClientCtxConf.instance) // line 52
                .onErrorReturnItem(false)
    }
}

As the thrown ConfException: ... is NULL! is on the error log, the ServiceClientCtxConf.instance seem to be null and further the mentioned ServiceClientCtxConf :

class ServiceClientCtxConf(
    val serviceClient: IServiceClient = ServiceClient()
) {

    companion object : ISingletonInstance<ServiceClientCtxConf> by SingletonInstance()
}

with this SingletonInstance:

interface ISingletonInstance<T> {

    var instance: T
}

class SingletonInstance<T> : ISingletonInstance<T> {

    private var singletonInstance: T? = null

    override var instance: T
        set(value) {
            singletonInstance = value
        }
        get() = singletonInstance ?: throw ConfException("... is NULL!") // get is here line 21
}

Is the SingletonInstance::set() never being called implicitly by ServiceClientCtxConf.instance?

What am I missing here?


Solution

  • Nobody calls SingletonInstance#instance#set method.

    First, property in Kotlin is just a class field with setter and getter provided. If property has non-primitive type its default initialization value is null.

    Second, companion object is a singleton and lazily initialized at the first call. When its type is an interface then an implementation must be provided. That's where provided by feature comes into play. In your case implementation provided by SingletonInstance().

    Last, when .setConf(context = ServiceClientCtxConf.instance) is called then it is converted to ServiceClientCtxConf.Companion.instance and getter is executed. Since no value provided via set then default is returned.

    Construction with companion object does not call set method implicitly. Moreover being a singleton it doesn't have even a reference to ServiceClientCtxConf.

    To fix that need to explicitly set ServiceClientCtxConf to Companion.instance.