Search code examples
androidkotlinsharedpreferences

How to use mutableSetOf in SharedPreferences in kotlin


Hey I am working in SharedPreferences in Android Kotlin. I setting value in mutableSetOf in SharedPreferences. I did this without any error but problem is my value is storing into single set item. I don't understand why this is causing the problem. But after storing value at that time I am reading value as well. It show me value with new data and if check in /data/data/com.example.activityexample/shared_prefs/vivek_pref.xml it only showing me single set of data. Can someone guide me how to use proper way.

MainActivity.kt

package com.example.activityexample

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.example.activityexample.databinding.FirstLayoutBinding
import org.koin.android.ext.android.inject

class MainActivity : AppCompatActivity() {

    companion object {
        private const val KEY = "Value"
    }

    private lateinit var binding: FirstLayoutBinding
    private val viewModel: ActivityViewModel by inject()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = FirstLayoutBinding.inflate(layoutInflater)
        setContentView(binding.root)
        readValue()
        setupOnClick()
    }

    private fun setupOnClick() {
        binding.nextActivity.setOnClickListener {
            val result = ++viewModel.value
            viewModel.saveSet(KEY, mutableSetOf(result.toString()))
            readValue()
        }
    }

    private fun readValue() {
        val value = viewModel.readSetValue(KEY)
        value?.forEach {
            println("preference value $it")
        }
    }
}

ActivityViewModel.kt

package com.example.activityexample

import android.app.Application
import android.content.Context
import androidx.lifecycle.AndroidViewModel

class ActivityViewModel(app: Application) : AndroidViewModel(app) {

    var value = 0
    private val appLifeSharedPreferences by lazy {
        app.getSharedPreferences("vivek_pref", Context.MODE_PRIVATE)
    }

    fun saveSet(
        prefsStorageKey: String,
        setToStore: MutableSet<String>
    ) {
        val valueInSet = readSetValue(prefsStorageKey)
        valueInSet?.addAll(setToStore)
        appLifeSharedPreferences
            .edit()
            .putStringSet(prefsStorageKey, valueInSet ?: setToStore)
            .apply()
    }

    fun readSetValue(prefsStorageKey: String): MutableSet<String>? {
        val prefs = appLifeSharedPreferences
        return if (prefs.contains(prefsStorageKey)) {
            prefs.getStringSet(prefsStorageKey, null)
        } else {
            null
        }
    }
}

Please see my video link. In my video you can see that I am clicking three times my click button and in console it's clearly printing the value. But if I checked in location where preference stores, it showing me single set of data. Even my data is not storing value after I restarted my application. It's removing the pervious value.

vivek_pref.xml

<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
    <set name="Value">
        <string>1</string>
    </set>
</map>

Solution

  • SharedPreferences under the hood reuses the MutableSet that it returns in getStringSet. This is why the documentation warns you:

    Note that you must not modify the set instance returned by this call. The consistency of the stored data is not guaranteed if you do, nor is your ability to modify the instance at all.

    You should copy the returned set immediately, and it's simpler to use read-only sets. I would also avoid unnecessary nullability because that just makes your code more verbose and less readable.

    class ActivityViewModel(app: Application) : AndroidViewModel(app) {
    
        var value = 0
        private val appLifeSharedPreferences by lazy {
            app.getSharedPreferences("vivek_pref", Context.MODE_PRIVATE)
        }
    
        fun saveSet(
            prefsStorageKey: String,
            setToStore: Set<String>
        ) {
            val valueInSet = readSetValue(prefsStorageKey)
            appLifeSharedPreferences
                .edit()
                .putStringSet(prefsStorageKey, valueInSet + setToStore)
                .apply()
        }
    
        fun readSetValue(prefsStorageKey: String): Set<String> {
            return appLifeSharedPreferences.getStringSet(prefsStorageKey, null)
                ?.toSet() // create a copy for safety's sake
                .orEmpty() // avoid unnecessary nullability
        }
    }
    

    As for the reason they designed it this way, they were probably trying to provide an optimization for cases where all you have to do is iterate the returned set immediately and synchronously and then forget about it.