Home > Software design >  Do you have to instantiate a Kotlin object?
Do you have to instantiate a Kotlin object?

Time:09-01

I am working on an Android Studio project where I am using a singleton class to keep track of data (I have already done research on the pros and cons of singleton objects, and I decided it was the best solution for my project). I am, however, running into a few problems that seem to point back to my singleton object, for which I have not been able to find any good solutions on StackOverflow or other developer forums.

  1. The first error I'm getting happens where I call the singleton object in another class.

Note: I am not instantiating this singleton class before using it, because if I understand Kotlin singletons correctly, you don't have to.

for (item in items) {
            with(item.device) {
                if (name == "BLE_DEVICE") {
                    count  
                    Data.addresses.add(address) >>> This is where I call the singleton object <<<
                }
            }
        }
  1. The second error I get comes from my initialization of SharedPreferences in the singleton class.
var sharedPref: SharedPreferences? = MainActivity().getSharedPreferences("MySharedPreferencesFile", Context.MODE_PRIVATE)
  1. The third error I get comes from calling this function from my singleton object.
fun saveSharedPreferences() {
        for ((key, value) in names) {
            if (sharedPref != null) {
                if (!sharedPref?.contains(key)!!) {
                    sharedPref
                        ?.edit()
                        ?.putString(key, value)
                        ?.apply()
                }
            }
        }
    }

FOR REFERENCE:

a. Here are the important lines from my stack trace...

2022-08-30 16:07:05.422 9946-9946/? E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.punchthrough.blestarterappandroid, PID: 9946
>>> java.lang.ExceptionInInitializerError
     >>> at com.punchthrough.blestarterappandroid.ScanResultAdapter.getItemCount(ScanResultAdapter.kt:62)
        ...
        ...
 >>> Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.SharedPreferences android.content.Context.getSharedPreferences(java.lang.String, int)' on a null object reference
    >>> at android.content.ContextWrapper.getSharedPreferences(ContextWrapper.java:174)
    >>> at com.punchthrough.blestarterappandroid.Data.<clinit>(Data.kt:35)
        at com.punchthrough.blestarterappandroid.ScanResultAdapter.getItemCount(ScanResultAdapter.kt:62) 

b. This is my singleton class used for tracking data.

object Data {
    // Format: <Device Address, Name>
    // Used for keeping a record of all the devices, keeping
    //  duplicate advertisements off the screen, and saving the
    //  user-inputted names to the MAC address
    var names: MutableMap<String, String> = mutableMapOf()

    // Format: <Device Address>
    // Used for keeping a count of how many views should be populated
    //  in the RecyclerView
    var addresses = mutableSetOf<String>()

    // TODO: Fix this line to resolve an initialization error
    var sharedPref: SharedPreferences? = MainActivity().getSharedPreferences("MySharedPreferencesFile", Context.MODE_PRIVATE)

    fun saveSharedPreferences() {
        for ((key, value) in names) {
            if (sharedPref != null) {
                if (!sharedPref?.contains(key)!!) {
                    sharedPref
                        ?.edit()
                        ?.putString(key, value)
                        ?.apply()
                }
            }
        }
    }

}

CodePudding user response:

Never instantiate an Activity. It simply won't work and cannot be used for any useful purpose. Activities are full of properties that are set up when the OS creates the Activity for you. If you instantiate it yourself, you have a dead object full of null properties that are not supposed to be null.

Kotlin's built-in singleton (object) is unsuitable for singletons that depend on something else because it has no constructor for you to call to initialize the dependencies.

In this case, your singleton would have to be dependent on a Context to be able to use shared preferences, so a Kotlin object is not suitable.

This is how you can create a singleton that needs a context:

class Data private constructor(val context: Context) {

    companion object {
        private var instance: Data? = null

        fun getInstance(context: Context): Data {
            return instance ?: synchronized(this) {
                instance ?: Data(context.applicationContext).also { instance = it }
            }
        }
    }

    val names: MutableMap<String, String> = mutableMapOf()

    val sharedPref: SharedPreferences = context.getSharedPreferences("MySharedPreferencesFile", Context.MODE_PRIVATE)

    fun saveSharedPreferences() { // I simplified this function a bit
        sharedPref.edit {
            for ((key, value) in names) {
                if (!sharedPref.contains(key)) {
                    putString(key, value)
                }
            }
        }
    }

}

And each time you use it, you would need to pass a Context to Data.getInstance.

By the way, I highly discourage combining var with MutableList or MutableSet. It invites mistakes because outside classes won't know whether they should swap out the collection for a new instance or mutate it in place when they want to make changes. And other classes cannot know whether it's safe to cache a copy of the list because it may or may not change out from under them based on something some other class is doing.

Really, I wouldn't recommend ever exposing a MutableCollection (MutableList or MutableSet) or a var read-only Collection publicly from any class. It leaves you open to many possible types of bugs when outside classes can change a collection that a class is using internally, or that is used by multiple classes. Instead, I would make the collections private and expose functions that indirectly modify them such as addName().

  • Related