SamAko
SamAko

Reputation: 3615

Writing Singleton Class To Manage Android SharedPreferences

i am trying to write a singleton class to oversee all operations involving shared preferences.

I have 3 preference files, general, settings, and temp

I want to be able to use this class to write a preference of a given type, for example:

stg_full_screen: true // as boolean

This is what i have done so far:

import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;

public class SharedPrefManager extends Activity {

    // Globals
    private int GENERAL             = 1000;
    private int SETTINGS            = 2000;
    private int TEMP_STORE          = 3000;

    private String PREF_GENERAL     = "com.example.general";
    private String PREF_SETTINGS    = "com.example.settings";
    private String PREF_TEMP_STORE  = "com.example.temp_store";


    private SharedPreferences general;
    private SharedPreferences settings;
    private SharedPreferences tempStore;

    private SharedPreferences.Editor general_editor;
    private SharedPreferences.Editor settings_editor;
    private SharedPreferences.Editor temp_store_editor;





    // Instantiate singleton object
    private static SharedPrefManager ourInstance = new SharedPrefManager();


    public static SharedPrefManager getInstance() { return ourInstance; }

    private SharedPrefManager() {
        // Get handle on all preference files
        general   = getSharedPreferences(PREF_GENERAL, Context.MODE_PRIVATE);
        settings  = getSharedPreferences(PREF_SETTINGS, Context.MODE_PRIVATE);
        tempStore = getSharedPreferences(PREF_TEMP_STORE, Context.MODE_PRIVATE);

        // provision editors for all preference files
        general_editor    = general.edit();
        settings_editor   = settings.edit();
        temp_store_editor = tempStore.edit();
    }



    private String read_prefs (String pref_name) {
        // this method reads a preference and returns it
        // ideally, i would want to be able to return appropriate types by request
        // e.g boolean, string
        return null;
    }

    private void write_prefs (String pref_name, String pref_val) {
        // this method would take a preference and write the appropriate type to prefs
    }


    // this method determines where to put a preference by checking the name of the key
    // this works because i use the following naming conventions
    // stg_name for settings, tmp_name for all that goes into tempStore

    private String resolve_pref_category (String path) {
        if (path.startsWith("stn"))         return PREF_SETTINGS;
        else if (path.startsWith("tmp"))    return PREF_TEMP_STORE;
        else                                return PREF_GENERAL;
    }

}

My question is:

  1. Is this a wise thing to do?
  2. How can i efficiently determine the type of a preference?

Thanks

Upvotes: 18

Views: 28006

Answers (7)

05nelsonm
05nelsonm

Reputation: 330

Kotlin example for dual purpose encrypted & unencrypted shared preferences using anrdoidx's security-crypto library (min API 23).


I use Dagger2 to inject this as a @Singleton where needed.

Use the @Name annotation in your Dagger Modules to differentiate between the SharedPreferences instances and you can have 2 separate .xml files (1 encrypted, 1 unencrypted) to read/write to/from.


Add to dependenies in build.gradle:

implementation "androidx.security:security-crypto:1.0.0-beta01"


import android.content.Context
import android.content.SharedPreferences
import androidx.security.crypto.EncryptedSharedPreferences

class Prefs(prefsName: String, context: Context) {

    private lateinit var ANDX_SECURITY_KEY_KEYSET: String
    private lateinit var ANDX_SECURITY_VALUE_KEYSET: String
    private lateinit var cntext: Context
    private lateinit var prefName: String

    private lateinit var prefs: SharedPreferences

    constructor(
        prefsName: String,
        context: Context,
        masterKeyAlias: String,
        prefKeyEncryptionScheme: EncryptedSharedPreferences.PrefKeyEncryptionScheme,
        prefValueEncryptionScheme: EncryptedSharedPreferences.PrefValueEncryptionScheme
    ): this(prefsName, context) {
        ANDX_SECURITY_KEY_KEYSET = "__androidx_security_crypto_encrypted_prefs_key_keyset__"
        ANDX_SECURITY_VALUE_KEYSET =    "__androidx_security_crypto_encrypted_prefs_value_keyset__"
        cntext = context
        prefName = prefsName
        prefs =
            EncryptedSharedPreferences.create(
                prefsName,
                masterKeyAlias,
                context,
                prefKeyEncryptionScheme,
                prefValueEncryptionScheme
            )
    }

    init {
        if (!::ANDX_SECURITY_KEY_KEYSET.isInitialized) {
            prefs =
                context.getSharedPreferences(
                    prefsName,
                    Context.MODE_PRIVATE
                )
        }
    }

    companion object {
        const val INVALID_BOOLEAN: Boolean = false
        const val INVALID_FLOAT: Float = -11111111111F
        const val INVALID_INT: Int = -1111111111
        const val INVALID_LONG: Long = -11111111111L
        const val INVALID_STRING: String = "INVALID_STRING"
        val INVALID_STRING_SET: Set<String> = setOf(INVALID_STRING)
    }

    /**
     * OnChangeListener
     * */
    fun registerOnSharedPreferenceChangeListener(
        listener: SharedPreferences.OnSharedPreferenceChangeListener) =
        prefs.registerOnSharedPreferenceChangeListener(listener)

    fun unregisterOnSharedPreferenceChangeListener(
        listener: SharedPreferences.OnSharedPreferenceChangeListener) =
        prefs.unregisterOnSharedPreferenceChangeListener(listener)

    /**
     * Read Shared Prefs
     * */
    fun contains(key: String): Boolean =
        prefs.contains(key)

    fun getAll(): Map<String, *> =
        prefs.all

    // Returns null if the Boolean value is not in
    //  Shared Preferences
    fun read(key: String): Boolean? =
        if (contains(key)) {
            read(key, INVALID_BOOLEAN)
        } else {
            null
        }

    // Boolean
    fun read(key: String, returnIfInvalid: Boolean): Boolean =
        prefs.getBoolean(key, returnIfInvalid)

    // Float
    fun read(key: String, returnIfInvalid: Float): Float =
        prefs.getFloat(key, returnIfInvalid)

    // Int
    fun read(key: String, returnIfInvalid: Int): Int =
        prefs.getInt(key, returnIfInvalid)

    // Long
    fun read(key: String, returnIfInvalid: Long): Long =
        prefs.getLong(key, returnIfInvalid)

    // Set<String>
    fun read(key: String, returnIfInvalid: Set<String>): Set<String>? =
        prefs.getStringSet(key, returnIfInvalid)

    // String
    fun read(key: String, returnIfInvalid: String): String? =
        prefs.getString(key, returnIfInvalid)

    /**
     * Modify Shared Prefs
     * */
    fun clear() {
        if (::ANDX_SECURITY_KEY_KEYSET.isInitialized) {
            val clearTextPrefs = cntext.getSharedPreferences(prefName, Context.MODE_PRIVATE)
            val keyKeyset = clearTextPrefs.getString(ANDX_SECURITY_KEY_KEYSET, INVALID_STRING)
            val valueKeyset = clearTextPrefs.getString(ANDX_SECURITY_VALUE_KEYSET, INVALID_STRING)
            if (keyKeyset != null && keyKeyset != INVALID_STRING
                && valueKeyset != null && valueKeyset != INVALID_STRING) {
                if (!clearTextPrefs.edit().clear().commit()) {
                    clearTextPrefs.edit().clear().apply()
                }
                if (!clearTextPrefs.edit().putString(ANDX_SECURITY_KEY_KEYSET, keyKeyset).commit()) {
                    clearTextPrefs.edit().putString(ANDX_SECURITY_KEY_KEYSET, keyKeyset).apply()
                }
                if (!clearTextPrefs.edit().putString(ANDX_SECURITY_VALUE_KEYSET, valueKeyset).commit()) {
                    clearTextPrefs.edit().putString(ANDX_SECURITY_VALUE_KEYSET, valueKeyset).apply()
                }
            }
        } else {
            if (!prefs.edit().clear().commit()) {
                prefs.edit().clear().apply()
            }
        }
    }

    fun remove(key: String) {
        if (!prefs.edit().remove(key).commit()) {
            prefs.edit().remove(key).apply()
        }
    }

    // Boolean
    fun write(key: String, value: Boolean) {
        if (!prefs.edit().putBoolean(key, value).commit()) {
            prefs.edit().putBoolean(key, value).apply()
        }
    }

    // Float
    fun write(key: String, value: Float) {
        if (!prefs.edit().putFloat(key, value).commit()) {
            prefs.edit().putFloat(key, value).apply()
        }
    }

    // Int
    fun write(key: String, value: Int) {
        if (!prefs.edit().putInt(key, value).commit()) {
            prefs.edit().putInt(key, value).apply()
        }
    }

    // Long
    fun write(key: String, value: Long) {
        if (!prefs.edit().putLong(key, value).commit()) {
            prefs.edit().putLong(key, value).apply()
        }
    }

    // Set<String>
    fun write(key: String, value: Set<String>) {
        if (!prefs.edit().putStringSet(key, value).commit()) {
            prefs.edit().putStringSet(key, value).apply()
        }
    }

    // String
    fun write(key: String, value: String) {
        if (!prefs.edit().putString(key, value).commit()) {
            prefs.edit().putString(key, value).apply()
        }
    }
}

An alternative option to using Dagger2 to inject as a @Singleton could be to:

AppPrefs.kt

object AppPrefs {
    lateinit var encryptedPrefs: Prefs
    lateinit var prefs: Prefs

    // Add your key strings here...

    fun initEncryptedPrefs(context: Context) {
        encryptedPrefs =
            Prefs(
                "ENCRYPTED_PREFS",
                context,
                MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC),
                EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
                EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
            )
    }

    fun initPrefs(context: Context) {
        prefs = Prefs("PREFS", context)
    }
}

Application.kt

class Application: Application() {
    override fun onCreate() {
        super.onCreate()
        AppPrefs.initEncryptedPrefs(this.applicationContext)
        AppPrefs.initPrefs(this.applicationContext)
    }
}

Then just call from where ever AppPrefs.prefs or AppPrefs.encryptedPrefs

Upvotes: 3

albaspazio
albaspazio

Reputation: 181

I propose my version, which start from Max Zonov answer. I create an abstract class, implementing read and write ops, independent from a specific project and thus suitable for being defined in a "core" module.

import android.content.SharedPreferences

abstract class SharedPreferenceWrapper {

    protected lateinit var prefs: SharedPreferences

    fun isInitialized() = ::prefs.isInitialized

    open fun read(key: String, defValue: Any): Any? {
        return when(defValue){
            is String   -> prefs.getString(key, defValue)
            is Int      -> prefs.getInt(key, defValue)
            is Boolean  -> prefs.getBoolean(key, defValue)
            is Long     -> prefs.getLong(key, defValue)
            is Float    -> prefs.getFloat(key, defValue)
            is Set<*>   -> {
                        if(defValue.isNotEmpty() && defValue.elementAt(0) is String)    prefs.getStringSet(key, defValue as Set<String>)
                        else                                                            return null
            }
            else        -> null
        }
    }

    open fun write(key: String, value: Any):Any? {
        val prefsEditor: SharedPreferences.Editor = prefs.edit()
        with(prefsEditor) {
            when(value){
                is String   -> putString(key, value)
                is Int      -> putInt(key, value)
                is Boolean  -> putBoolean(key, value)
                is Long     -> putLong(key, value)
                is Float    -> putFloat(key, value)
                is Set<*>   -> {
                                if(value.isNotEmpty() && value.elementAt(0) is String)    putStringSet(key, value as Set<String>)
                                else                                                            return null
                }
                else        -> return null
            }
            commit()
        }
        return value
    }
}

And then, in my specific project, I create a singleton that init the SharedPreference previously defined and do a sanity check on given keys.

import android.content.Context
import androidx.preference.PreferenceManager
import org.albaspazio.core.sharedpreferences.SharedPreferenceWrapper


object PsySuitePreferences: SharedPreferenceWrapper() {

    private val validKeys:List<String> = listOf(
        "pref_delay_a1", "pref_delay_a2", "pref_delay_a3",
        "pref_delay_t1", "pref_delay_t2",
        "pref_delay_v1", "pref_delay_v2",
        "pref_main_email")


    //call it once
    fun init(context:Context, pref_name:String="", mode:Int = Context.MODE_PRIVATE){

        if(isInitialized()) return      // prevent multiple init

        prefs = if(pref_name.isEmpty()) PreferenceManager.getDefaultSharedPreferences(context)
                else                    context.getSharedPreferences(pref_name, mode)
    }

    override fun read(key: String, defValue: Any): Any?{
        return  if(!validKeys.contains(key) || !isInitialized()) null
                else super.read(key, defValue)
    }

    override fun write(key: String, value: Any): Any?{
        return  if(!validKeys.contains(key) || !isInitialized())    null
                else super.write(key, value)
    }
}

ciao

Upvotes: 1

Mahi Saini
Mahi Saini

Reputation: 219

import android.content.Context; import android.content.SharedPreferences;

import com.example.day.weeklypapers.model.LoginModel; import com.google.gson.Gson;

public class WeeklyPreference {

private static WeeklyPreference mInstance = null;
private static SharedPreferences mPreferences;
private static SharedPreferences.Editor mEditor;
private Context context;
private static String SharedPreferenceKey = "Weekly"   ;

private WeeklyPreference() {

}

public static WeeklyPreference getInstance(Context context) {
    if (mInstance == null) {
        mInstance = new WeeklyPreference();
    }
    if (mPreferences == null) {
        mPreferences = context.getApplicationContext().getSharedPreferences(SharedPreferenceKey, Context.MODE_PRIVATE);
        mEditor = mPreferences.edit();
        mEditor.commit();
    }
    return mInstance;
}

public void saveInPreference(String key, String value) {
    mEditor.putString(key, value);
    mEditor.commit();
}

public String getFromPreference(String key) {
    return mPreferences.getString(key, "");
}

public LoginModel getUserDetails() {
    String userJson = mPreferences.getString(PrefrenceConstants.KEY_USER_JSON_DETAILS, "");
    LoginModel user = null;
    if (userJson != null && !userJson.equals("")) {
        user = new Gson().fromJson(userJson, LoginModel.class);
    }
    return user;
}

public void saveUserDetails(LoginModel user) {
    mEditor.putString(PrefrenceConstants.KEY_USER_JSON_DETAILS, new Gson().toJson(user));
    mEditor.commit();
}

public boolean isAutoLogin() {
    String userJson = mPreferences.getString(PrefrenceConstants.KEY_USER_JSON_DETAILS, "");
    LoginModel user = null;
    if (userJson != null && !userJson.equals("")) {
        user = new Gson().fromJson(userJson, LoginModel.class);
        return user != null;
    } else {
        return false;
    }
}

}

You can pass direct your pojo class or Model class in this singleton class reduce your effort

Take Care .. Enjoy..

Upvotes: 1

VenturaSystems
VenturaSystems

Reputation: 19

SharedPreferences Singleton with ApplicationContext

public class SharedPrefsSingleton {

    private SharedPreferences sharedPref;
    private Context appContext;

    private static SharedPrefsSingleton instance;

    public static synchronized SharedPrefsSingleton getInstance(Context applicationContext){
        if(instance == null)
            instance = new SharedPrefsSingleton(applicationContext);
        return instance;
    }

    private SharedPrefsSingleton(Context applicationContext) {
        appContext = applicationContext;
        sharedPref = appContext.getSharedPreferences(
                appContext.getString(R.string.key_pref), Context.MODE_PRIVATE );
    }

    public void writeData(float value) {
        SharedPreferences.Editor editor = sharedPref.edit();
        editor.putFloat(appContext.getString(R.string.key_data), value);
        editor.apply();
    }

    public float readData() {
        return sharedPref.getFloat(appContext.getString(R.string.key_data), 0);
    }
}

Upvotes: 1

Magesh Pandian
Magesh Pandian

Reputation: 9369

Proper Singleton Shared Preferences Class. it may help others in the future.

public class SharedPref
{
    private static SharedPreferences mSharedPref;
    public static final String NAME = "NAME";
    public static final String AGE = "AGE";
    public static final String IS_SELECT = "IS_SELECT";

    private SharedPref()
    {

    }

    public static void init(Context context)
    {
        if(mSharedPref == null)
            mSharedPref = context.getSharedPreferences(context.getPackageName(), Activity.MODE_PRIVATE);
    }

    public static String read(String key, String defValue) {
        return mSharedPref.getString(key, defValue);
    }

    public static void write(String key, String value) {
        SharedPreferences.Editor prefsEditor = mSharedPref.edit();
        prefsEditor.putString(key, value);
        prefsEditor.commit();
    }

    public static boolean read(String key, boolean defValue) {
        return mSharedPref.getBoolean(key, defValue);
    }

    public static void write(String key, boolean value) {
        SharedPreferences.Editor prefsEditor = mSharedPref.edit();
        prefsEditor.putBoolean(key, value);
        prefsEditor.commit();
    }

    public static Integer read(String key, int defValue) {
        return mSharedPref.getInt(key, defValue);
    }

    public static void write(String key, Integer value) {
        SharedPreferences.Editor prefsEditor = mSharedPref.edit();
        prefsEditor.putInt(key, value).commit();
    }
}

Simply call SharedPref.init() on MainActivity once

SharedPref.init(getApplicationContext());

Write data

    SharedPref.write(SharedPref.NAME, "XXXX");//save string in shared preference.
    SharedPref.write(SharedPref.AGE, "25");//save int in shared preference.
    SharedPref.write(SharedPref.IS_SELECT, true);//save boolean in shared preference.

Read Data

    String name = SharedPref.read(SharedPref.NAME, null);//read string in shared preference.
    String age = SharedPref.read(SharedPref.AGE, 0);//read int in shared preference.
    String isSelect = SharedPref.read(SharedPref.IS_SELECT, false);//read boolean in shared preference.

Output

Name : "XXXX";
Age : "25";
IsSelect : "true";

Upvotes: 39

Neuron
Neuron

Reputation: 1129

Kotlin solution:

object PrefsHelper {

    private lateinit var prefs: SharedPreferences

    private const val PREFS_NAME = "params"

    const val ID_USER = "id_user"
    const val TOKEN = "token"

    fun init(context: Context) {
        prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
    }

    fun read(key: String, value: String): String? {
        return prefs.getString(key, value)
    }

    fun read(key: String, value: Long): Long? {
        return prefs.getLong(key, value)
    }

    fun write(key: String, value: String) {
        val prefsEditor: SharedPreferences.Editor = prefs.edit()
        with(prefsEditor) {
            putString(key, value)
            commit()
        }
    }

    fun write(key: String, value: Long) {
        val prefsEditor: SharedPreferences.Editor = prefs.edit()
        with(prefsEditor) {
            putLong(key, value)
            commit()
        }
    }
}

Call init() function on first app’s run.

Upvotes: 24

Artem Zinnatullin
Artem Zinnatullin

Reputation: 4447

Usually, I use something like this:

No static Context reference, static getter/setter for each property, when required you can add memory cached value for some property to get it faster from memory instead of reading from SharedPreferences. Clear api.

public class SharedPreferencesManager {

    private static final String APP_SETTINGS = "APP_SETTINGS";


    // properties
    private static final String SOME_STRING_VALUE = "SOME_STRING_VALUE";
    // other properties...


    private SharedPreferencesManager() {}

    private static SharedPreferences getSharedPreferences(Context context) {
        return context.getSharedPreferences(APP_SETTINGS, Context.MODE_PRIVATE);
    }

    public static String getSomeStringValue(Context context) {
        return getSharedPreferences(context).getString(SOME_STRING_VALUE , null);
    }

    public static void setSomeStringValue(Context context, String newValue) {
        final SharedPreferences.Editor editor = getSharedPreferences(context).edit();
        editor.putString(SOME_STRING_VALUE , newValue);
        editor.commit();
    }

    // other getters/setters
}

Upvotes: 31

Related Questions