Reputation: 3615
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:
Thanks
Upvotes: 18
Views: 28006
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
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
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
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
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
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
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