Code Review Best way use SharedPreferences solving MemoryLeak - android

i try to resolve memory leak in Shared Preferences use, i try all day to do this, but still confuse, my goal is possible call pref in anywhere i want. here my code.
class Preferences (private val context: Context) {
private val sharedPreferences: SharedPreferences =
context.getSharedPreferences(context.packageName+"_pref", Context.MODE_PRIVATE)
private val editor: SharedPreferences.Editor
companion object {
private val KEY_USER = "user"
private val KEY_EMAIL = "email"
}
init {
editor = sharedPreferences.edit()
}
private fun isKeyExist(Key: String): Boolean = sharedPreferences.contains(Key)
private fun putString(Key: String, value: String) {
editor.putString(Key, value)
editor.apply()
}
private fun putInt(Key: String, value: Int) {
editor.putInt(Key, value)
editor.apply()
}
private fun putBoolean(Key: String, value: Boolean) {
editor.putBoolean(Key, value)
editor.apply()
}
private fun putDouble(key: String, value: Double) {
editor.putLong(key, java.lang.Double.doubleToRawLongBits(value))
}
private fun getInt(Key: String): Int = sharedPreferences.getInt(Key, 0)
private fun getString(Key: String): String = sharedPreferences.getString(Key, "")
private fun getBoolean(key: String): Boolean = sharedPreferences.getBoolean(key, false)
private fun getLong(key: String): Long = sharedPreferences.getLong(key, 0)
fun init (){
if(!isKeyExist(KEY_USER)){
putString(KEY_USER,"")
}
if(!isKeyExist(KEY_EMAIL)){
putString(KEY_EMAIL,"")
}
}
private fun resetPref(){
if(isKeyExist(KEY_USER)){
putString(KEY_USER,"")
}
if(isKeyExist(KEY_EMAIL)){
putString(KEY_EMAIL,"")
}
}
var user : String
get() = getString(KEY_USER)
set(value) = putString(KEY_USER,value)
var email : String
get() = getString(KEY_EMAIL)
set(value) = putString(KEY_EMAIL,value)
Because pref need context, i init pref in some class with extend Application like code below,
class BaseApplication : android.app.Application() {
override fun onCreate() {
super.onCreate()
preferences = Preferences(applicationContext)
}
companion object {
#SuppressLint("StaticFieldLeak")
var preferences : Preferences? = null
}
with this method, it's possible to call pref anywhere like activity, fragment, or some class with no context, with this simple way,
BaseApplication.preferences!!.user
but it will make memory leak in my apps.
will appreciate if any someone can give me some advice how to resolve memory leak.

We call this use as a singleton pattern. Use this class :
public class Prefs {
private static final String TAG = "Prefs";
static Prefs singleton = null;
static SharedPreferences preferences;
static SharedPreferences.Editor editor;
private static Gson GSON = new Gson();
Type typeOfObject = new TypeToken<Object>() {
}.getType();
Prefs(Context context) {
preferences = context.getSharedPreferences(TAG, Context.MODE_PRIVATE);
editor = preferences.edit();
}
public static Prefs with(Context context) {
if (singleton == null) {
singleton = new Builder(context).build();
}
return singleton;
}
public void save(String key, boolean value) {
editor.putBoolean(key, value).apply();
}
public void save(String key, String value) {
editor.putString(key, value).apply();
}
public void save(String key, int value) {
editor.putInt(key, value).apply();
}
public void save(String key, float value) {
editor.putFloat(key, value).apply();
}
public void save(String key, long value) {
editor.putLong(key, value).apply();
}
public void save(String key, Set<String> value) {
editor.putStringSet(key, value).apply();
}
// to save object in prefrence
public void save(String key, Object object) {
if (object == null) {
throw new IllegalArgumentException("object is null");
}
if (key.equals("") || key == null) {
throw new IllegalArgumentException("key is empty or null");
}
editor.putString(key, GSON.toJson(object)).apply();
}
// To get object from prefrences
public <T> T getObject(String key, Class<T> a) {
String gson = preferences.getString(key, null);
if (gson == null) {
return null;
} else {
try {
return GSON.fromJson(gson, a);
} catch (Exception e) {
throw new IllegalArgumentException("Object storaged with key "
+ key + " is instanceof other class");
}
}
}
public boolean getBoolean(String key, boolean defValue) {
return preferences.getBoolean(key, defValue);
}
public String getString(String key, String defValue) {
return preferences.getString(key, defValue);
}
public int getInt(String key, int defValue) {
return preferences.getInt(key, defValue);
}
public float getFloat(String key, float defValue) {
return preferences.getFloat(key, defValue);
}
public long getLong(String key, long defValue) {
return preferences.getLong(key, defValue);
}
public Set<String> getStringSet(String key, Set<String> defValue) {
return preferences.getStringSet(key, defValue);
}
public Map<String, ?> getAll() {
return preferences.getAll();
}
public void remove(String key) {
editor.remove(key).apply();
}
public void removeAll() {
editor.clear();
editor.apply();
}
private static class Builder {
private final Context context;
public Builder(Context context) {
if (context == null) {
throw new IllegalArgumentException("Context must not be null.");
}
this.context = context.getApplicationContext();
}
/**
* Method that creates an instance of Prefs
*
* #return an instance of Prefs
*/
public Prefs build() {
return new Prefs(context);
}
}
}
The kotlin version:
class Prefs internal constructor(context: Context) {
internal var typeOfObject = object : TypeToken<Any>() {
}.type
val all: Map<String, *>
get() = preferences.all
init {
preferences = context.getSharedPreferences(TAG, Context.MODE_PRIVATE)
editor = preferences.edit()
}
fun save(key: String, value: Boolean) {
editor.putBoolean(key, value).apply()
}
fun save(key: String, value: String) {
editor.putString(key, value).apply()
}
fun save(key: String, value: Int) {
editor.putInt(key, value).apply()
}
fun save(key: String, value: Float) {
editor.putFloat(key, value).apply()
}
fun save(key: String, value: Long) {
editor.putLong(key, value).apply()
}
fun save(key: String, value: Set<String>) {
editor.putStringSet(key, value).apply()
}
// to save object in prefrence
fun save(key: String?, `object`: Any?) {
if (`object` == null) {
throw IllegalArgumentException("object is null")
}
if (key == "" || key == null) {
throw IllegalArgumentException("key is empty or null")
}
editor.putString(key, GSON.toJson(`object`)).apply()
}
// To get object from prefrences
fun <T> getObject(key: String, a: Class<T>): T? {
val gson = preferences.getString(key, null)
return if (gson == null) {
null
} else {
try {
GSON.fromJson(gson, a)
} catch (e: Exception) {
throw IllegalArgumentException("Object storaged with key "
+ key + " is instanceof other class")
}
}
}
fun getBoolean(key: String, defValue: Boolean): Boolean {
return preferences.getBoolean(key, defValue)
}
fun getString(key: String, defValue: String): String? {
return preferences.getString(key, defValue)
}
fun getInt(key: String, defValue: Int): Int {
return preferences.getInt(key, defValue)
}
fun getFloat(key: String, defValue: Float): Float {
return preferences.getFloat(key, defValue)
}
fun getLong(key: String, defValue: Long): Long {
return preferences.getLong(key, defValue)
}
fun getStringSet(key: String, defValue: Set<String>): Set<String>? {
return preferences.getStringSet(key, defValue)
}
fun remove(key: String) {
editor.remove(key).apply()
}
fun removeAll() {
editor.clear()
editor.apply()
}
private class Builder(context: Context?) {
private val context: Context
init {
if (context == null) {
throw IllegalArgumentException("Context must not be null.")
}
this.context = context.applicationContext
}
/**
* Method that creates an instance of Prefs
*
* #return an instance of Prefs
*/
fun build(): Prefs {
return Prefs(context)
}
}
companion object {
private val TAG = "Prefs"
lateinit var singleton: Prefs
lateinit var preferences: SharedPreferences
lateinit var editor: SharedPreferences.Editor
private val GSON = Gson()
fun with(context: Context): Prefs {
if (singleton == null) {
singleton = Builder(context).build()
}
return singleton
}
}
}
You will need google GSON to save objects.
Calling it is like this :
Prefs.with(context).save("key","value or object or int or boolean");
Hope this helps.

Related

How do I use SharedPreferences to getString in Kotlin (android)?

I just can't figure out how to use this a fragment. The official documentation shows this example:
val sharedPref = activity?.getPreferences(Context.MODE_PRIVATE) ?: return
val defaultValue = resources.getInteger(R.integer.saved_high_score_default_key)
val highScore = sharedPref.getInt(getString(R.string.saved_high_score_key), defaultValue)
Replacing "getInt" with "getString" always returns ""
val sharedPref = activity?.getPreferences(Context.MODE_PRIVATE)
val password = sharedPref?.getString("key", "")
if (key == "") {
// this always calls even when I change the value from the settings menu!
} else {
}
This is the simple class use for store any data type (String, Boolean, Int) in SharedPreference:
class PrefUtil(context: Context) {
private val context: Context
val PREFS_NAME = "my_prefs"
fun setInt(key: String?, value: Int) {
val prefs: SharedPreferences = context.getSharedPreferences(PREFS_NAME, 0)
val editor: SharedPreferences.Editor = prefs.edit()
editor.putInt(key, value)
editor.apply()
}
fun getInt(key: String?, defValue: Int): Int {
val prefs: SharedPreferences = context.getSharedPreferences(PREFS_NAME, 0)
return prefs.getInt(key, defValue)
}
fun setString(key: String?, value: String?) {
val prefs: SharedPreferences = context.getSharedPreferences(PREFS_NAME, 0)
val editor: SharedPreferences.Editor = prefs.edit()
editor.putString(key, value)
editor.apply()
}
fun getString(key: String?): String? {
val prefs: SharedPreferences = context.getSharedPreferences(PREFS_NAME, 0)
return prefs.getString(key, "null")
}
fun setBool(key: String?, value: Boolean) {
val prefs: SharedPreferences = context.getSharedPreferences(PREFS_NAME, 0)
val editor: SharedPreferences.Editor = prefs.edit()
editor.putBoolean(key, value)
editor.apply()
}
fun getBool(key: String?): Boolean {
val prefs: SharedPreferences = context.getSharedPreferences(PREFS_NAME, 0)
return prefs.getBoolean(key, false)
}
fun getBool(key: String?, defaultValue: Boolean): Boolean {
val prefs: SharedPreferences = context.getSharedPreferences(PREFS_NAME, 0)
return prefs.getBoolean(key, defaultValue)
}
init {
this.context = context
}
}
and for store String use:
PrefUtil(this).setString("Key","value")
for getting String from SharedPreference use:
PrefUtil(this).getString("key")
You need to insert a piece of data for this key first, and then call get method:
val sharedPref = activity?.getPreferences(Context.MODE_PRIVATE)
with (sharedPref.edit()) {
putString("key", "abc123456")
apply()
}
val password = sharedPref?.getString("key", "")
if (password == "") {
// ....
Log.d(TAG,"password is empty.")
} else {
Log.d(TAG,">>>:$password")
}
You can try like this
//For Add String Value in sharedPreferences
val sharedPreferences = getSharedPreferences("key", MODE_PRIVATE) ?: return
with(sharedPreferences.edit()) {
putString("yourStringKey", "Hello World")
apply()
}
//Here get enter string value from sharedPreferences other activity
val sharedPreferences1 = getSharedPreferences("key", MODE_PRIVATE) ?: return
val string = sharedPreferences1.getString("yourStringKey", "hi") //"hi" is default value
Log.e("sharedPreferences1", "sharedPreferences1 Val is -->> $string")
Turns out I actually needed to use SharedPreferences as I'm using a Settings Activity. Thanks for the answers though as they kind of helped to lead in the direction I needed.

Resources not getting updated after changing locale

My app supports two languages(English and Spanish). When I change the locale from English(en) to Spanish(es) it succeeds (Locale get changed). But, resources are not getting updated. I am recreating the activity after updated the Locale.
I have debugged the app on Splash screen after changing the Locale. and check the current Locale (returned 'es'). but still, the strings are not getting updated. The Spanish string file is put under the values-es package.
Also tried running the same localization code by creating a new application and it worked.
Following is the code:
VerifiApp.kt:
class VerifiApp : Application(), HasActivityInjector {
#Inject
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Activity>
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) {
Timber.plant(Timber.DebugTree())
}
AppInjector.init(this)
}
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(LocaleHelper.onAttach(base!!, "en"))
}
override fun activityInjector() = dispatchingAndroidInjector
}
SelectLanguageActivity.kt:
class SelectLanguageActivity : BaseActivity<SelectLanguageViewModel>() {
private var languageCode = ""
#Inject
lateinit var appDataManager: AppDataManager
#Inject
lateinit var appViewModelFactory: AppViewModelFactory
private lateinit var selectLanguageViewModel: SelectLanguageViewModel
private lateinit var languageAdapter: ArrayAdapter<Language>
private var languageList = ArrayList<Language>()
override fun getViewModel(): SelectLanguageViewModel {
selectLanguageViewModel = ViewModelProviders.of(this#SelectLanguageActivity, appViewModelFactory)
.get(SelectLanguageViewModel::class.java)
return selectLanguageViewModel
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_select_language)
languageList.add(Language("English", "en"))
languageList.add(Language("Spanish", "es"))
languageAdapter = ArrayAdapter(this, R.layout.item_spinner_dropdown, languageList)
spinnerLanguage.adapter = languageAdapter
spinnerLanguage.apply {
onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onNothingSelected(parent: AdapterView<*>?) {
}
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
languageCode = languageList[position].languageCode
}
}
}
btnUpdate.setOnClickListener {
val context = LocaleHelper.setLocale(this, languageCode)
val resources = context.resources
val bundle = Bundle()
bundle.putString("language_code", languageCode)
val intent = Intent(applicationContext, AuthActivity::class.java)
intent.putExtras(bundle)
startActivity(intent)
}
}
}
BaseActivity.kt:
abstract class BaseActivity<out V : ViewModel> : AppCompatActivity(), BaseFragment.Callback {
private lateinit var mViewModel: V
private lateinit var progressDialog: Dialog
override fun onCreate(savedInstanceState: Bundle?) {
performDependencyInjection()
super.onCreate(savedInstanceState)
initializeProgressLoader()
}
private fun initializeProgressLoader() {
progressDialog = Dialog(this)
progressDialog.setCancelable(false)
progressDialog.requestWindowFeature(Window.FEATURE_NO_TITLE)
progressDialog.window.setBackgroundDrawableResource(android.R.color.transparent)
progressDialog.setContentView(R.layout.dialog_progress)
}
fun setProgressVisibility(visible: Boolean) {
if (visible) {
progressDialog.show()
} else {
progressDialog.dismiss()
}
}
private fun performDependencyInjection() {
AndroidInjection.inject(this)
mViewModel = getViewModel()
}
override fun onFragmentAttached() {
}
override fun onFragmentDetached(tag: String) {
}
fun isNetworkConnected(): Boolean {
val flag = NetworkUtils.isNetworkConnected(applicationContext)
if (!flag) {
showErrorToast("Internet not connected!")
}
return flag
}
fun hideKeyboard() {
val view: View? = this.currentFocus
val inputMethodManager: InputMethodManager =
getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
inputMethodManager.hideSoftInputFromWindow(view?.windowToken, 0)
}
fun showErrorToast(message: String) {
ColorToast.makeText(this, message, ColorToast.LENGTH_SHORT, ColorToast.ERROR, false).show()
}
fun showInfoToast(message: String) {
ColorToast.makeText(this, message, ColorToast.LENGTH_SHORT, ColorToast.INFO, false).show()
}
fun showSuccessToast(message: String) {
ColorToast.makeText(this, message, ColorToast.LENGTH_SHORT, ColorToast.SUCCESS, false).show()
}
/**
* Override for set view model
*
* #return ViewModel instance
* */
abstract fun getViewModel(): V
override fun attachBaseContext(newBase: Context?) {
super.attachBaseContext(LocaleHelper.onAttach(newBase!!))
}
}
LocaleHelper.java
public class LocaleHelper {
private static final String SELECTED_LANGUAGE = "Locale.Helper.Selected.Language";
public static Context onAttach(Context context) {
String lang = getPersistedData(context, Locale.getDefault().getLanguage());
return setLocale(context, lang);
}
public static Context onAttach(Context context, String defaultLanguage) {
String lang = getPersistedData(context, defaultLanguage);
return setLocale(context, lang);
}
public static String getLanguage(Context context) {
return getPersistedData(context, Locale.getDefault().getLanguage());
}
public static Context setLocale(Context context, String language) {
persist(context, language);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return updateResources(context, language);
}
return updateResourcesLegacy(context, language);
}
private static String getPersistedData(Context context, String defaultLanguage) {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
return preferences.getString(SELECTED_LANGUAGE, defaultLanguage);
}
private static void persist(Context context, String language) {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = preferences.edit();
editor.putString(SELECTED_LANGUAGE, language);
editor.apply();
}
#TargetApi(Build.VERSION_CODES.N)
private static Context updateResources(Context context, String language) {
Locale locale = new Locale(language);
Locale.setDefault(locale);
Configuration configuration = context.getResources().getConfiguration();
configuration.setLocale(locale);
configuration.setLayoutDirection(locale);
return context.createConfigurationContext(configuration);
}
#SuppressWarnings("deprecation")
private static Context updateResourcesLegacy(Context context, String language) {
Locale locale = new Locale(language);
Locale.setDefault(locale);
Resources resources = context.getResources();
Configuration configuration = resources.getConfiguration();
configuration.locale = locale;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
configuration.setLayoutDirection(locale);
}
resources.updateConfiguration(configuration, resources.getDisplayMetrics());
return context;
}
}
Help would be really appreciated. Thanks.

java.lang.IllegalStateException: context.getSharedPrefere…EF, Context.MODE_PRIVATE) must not be null

This is my User class
open class User(val context: Context) : Serializable {
#SerializedName("token")
var tokenFromServer: String = ""
var tokenSharedPreferences: SharedPreferences =context.getSharedPreferences(Constants.USER_TOKEN_PREF, Context.MODE_PRIVATE)
#SerializedName("registered_token")
var token: String = ""
set(userNewToken) {
field = userNewToken
with(tokenSharedPreferences.edit()) {
putString(Constants.TOKEN_KEY, userNewToken).commit()
}
checkLogin() // 토큰이 설정 되면 로그인 상태 변경해준다
}
fun checkLogin(): Boolean {
with(context.getSharedPreferences(Constants.USER_TOKEN_PREF, Context.MODE_PRIVATE).getString(Constants.TOKEN_KEY, Constants.EMPTY_TOKEN)) {
if (this == Constants.EMPTY_TOKEN) return false else return true
}
return false
}
}
This is my test code
#RunWith(MockitoJUnitRunner::class)
class UserModelTest : InstrumentationTestCase() {
private val TEST_TOKEN_FROM_SERVER = "token"
#Mock
private lateinit var mockUserModel: User
private lateinit var realUserModel: User
var testContext = mock(Context::class.java)
#Before
fun initMocks() {
testContext = MockContext()
mockUserModel = User(testContext)
realUserModel = User(testContext)
given(realUserModel.tokenSharedPreferences.getString(eq(Constants.TOKEN_KEY), anyString()))
.willReturn(realUserModel.token)
}
}
I got "java.lang.IllegalStateException: context.getSharedPrefere…EF, Context.MODE_PRIVATE) must not be null" this error message.
Your code context.getSharedPreferences(Constants.USER_TOKEN_PREF, Context.MODE_PRIVATE) returns null so variable to store this value must be nullable:
var tokenSharedPreferences: SharedPreferences? =context.getSharedPreferences(Constants.USER_TOKEN_PREF, Context.MODE_PRIVATE)

Getting Error for not Initializing Instance of class in my SharedPrefManager class.Not able to solve

SharedPrefManager.kt
class SharedPrefManager private constructor(context: Context) {
lateinit var mCtx: Context
fun isLoggedIn(): Boolean {
val sharedPreferences = mCtx.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE)
if (sharedPreferences.getString(KEY_USERNAME, null) != null) {
return true
}
return false
}
fun username(): String {
val sharedPreferences = mCtx.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE)
return sharedPreferences.getString(KEY_USERNAME, null)
}
fun userEmail(): String {
val sharedPreferences = mCtx.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE)
return sharedPreferences.getString(KEY_USER_EMAIL, null)
}
init {
mCtx = context
}
fun userLogin(id: Int, username: String, email: String): Boolean {
val sharedPreferences = mCtx.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE)
val editor = sharedPreferences.edit()
editor.putInt(KEY_USER_ID, id)
editor.putString(KEY_USER_EMAIL, email)
editor.putString(KEY_USERNAME, username)
editor.apply()
return true
}
fun logout(): Boolean {
val sharedPreferences = mCtx.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE)
val editor = sharedPreferences.edit()
editor.clear()
editor.apply()
return true
}
companion object {
lateinit var mInstance: SharedPrefManager
private val SHARED_PREF_NAME = "mysharedpref12"
private val KEY_USERNAME = "username"
private val KEY_USER_EMAIL = "useremail"
private val KEY_USER_ID = "userid"
#Synchronized
fun getInstance(context: Context): SharedPrefManager {
if (mInstance == null) {
line:59-----> mInstance = SharedPrefManager(context)
}
return mInstance
}
}
}
This is the error which i am getting
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.shikh.kotlinmysql, PID: 6600
kotlin.UninitializedPropertyAccessException: lateinit property mInstance has not been initialized
at com.example.shikh.kotlinmysql.SharedPrefManager$Companion.getMInstance(SharedPrefManager.kt:59)
at com.example.shikh.kotlinmysql.SharedPrefManager$Companion.getInstance(SharedPrefManager.kt:66)
at com.example.shikh.kotlinmysql.Login$userLogin$stringRequest$2.onResponse(Login.kt:34)
at com.example.shikh.kotlinmysql.Login$userLogin$stringRequest$2.onResponse(Login.kt:29)
Login.kt
class Login : AppCompatActivity() , View.OnClickListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
buttonLogIn.setOnClickListener(this)
}
private fun userLogin() {
val username = editTextUser.text.toString().trim()
val password = editTextPass.text.toString().trim()
line:29----> val stringRequest = object : StringRequest(Request.Method.POST, Constants.URL_LOGIN,
Response.Listener<String> { response ->
try {
val obj = JSONObject(response)
if (!obj.getBoolean("error")) {
line:34----> SharedPrefManager.getInstance(applicationContext).userLogin(
obj.getInt("id"),
obj.getString("username"),
obj.getString("email")
)
Toast.makeText(applicationContext, "User log in Succesful", Toast.LENGTH_LONG).show()
} else {
Toast.makeText(applicationContext, obj.getString("message"), Toast.LENGTH_LONG).show()
}
} catch (e: JSONException) {
e.printStackTrace()
}
},
object : Response.ErrorListener {
override fun onErrorResponse(error: VolleyError) {
Toast.makeText(applicationContext, error.message, Toast.LENGTH_SHORT).show()
}
}) {
#Throws(AuthFailureError::class)
override fun getParams(): Map<String, String> {
val params = HashMap<String, String>()
params.put("username", username)
params.put("password", password)
return params
}
}
}
override fun onClick(v: View?) {
if (v==buttonLogIn){
userLogin()
}
}
}
If you're on Kotlin 1.2 you should check if mInstance is initialised like this:
if (!::mInstance.isInitialized) {
mInstance = SharedPrefManager(context)
}

How could I use Context Wrapper in order to save SharedPreferences in an external sd card folder in Android?

You can check this stackoverflow answer which uses a trick to override database path method in order to save db in sd card.
We are trying to do something similar with shared preferences without having to write and read files manually to sd card.
After a long search we found that there is this method getDataDirFile inside Android source code (android.app.ContextImpl) which is private and therefore cannot be overridden.
So is there another way to circumvent it?
Thanks
Sounds like a lot of work for something that is pretty easy. I just use this method:
public static Map<String, ?> getAll(Context context) {
SharedPreferences sharedPrefs = PreferenceManager
.getDefaultSharedPreferences(context);
return sharedPrefs.getAll();
}
The map is serializable hence easy to save in a file or on a cloud.
I use this method to load the preferences into my app:
#SuppressWarnings("unchecked")
public static void loadPreferences(Context context, Map<String, ?> prefs) {
SharedPreferences sharedPrefs = PreferenceManager
.getDefaultSharedPreferences(context);
for (String key : prefs.keySet()) {
Object pref = prefs.get(key);
if (pref instanceof Boolean) {
sharedPrefs.edit().putBoolean(key, (Boolean) pref).commit();
}
if (pref instanceof Float) {
sharedPrefs.edit().putFloat(key, (Float) pref).commit();
}
if (pref instanceof Integer) {
sharedPrefs.edit().putInt(key, (Integer) pref).commit();
}
if (pref instanceof Long) {
sharedPrefs.edit().putLong(key, (Long) pref).commit();
}
if (pref instanceof String) {
sharedPrefs.edit().putString(key, (String) pref).commit();
}
if (pref instanceof Set<?>) {
sharedPrefs.edit().putStringSet(key, (Set<String>) pref)
.commit();
}
}
}
There are single datas that need to be saved by key name. This is why preferences are so useful.
However preferences can be distinguished in two parts. Those that are real settings that the user interacts with in the PreferenceActivity and those that are merely used to save temporary or permanent data that do not correspond explicitly to a check-box or something similar.
As far as the former goes, seems that cYrixmorten's answer is the best available but you have to be aware of some maintenance.
The latter could be saved in database in a specific table with two columns, one for key and one for value.
The following code implements such an idea and is created to give a similar experience of ease of use like native android preferences.
This Scala code is not considered the most elegant solution. Use it with care.
package myAndroidSqlite
import android.content.{ContentValues, Context}
import models.DatabaseHandler
/**
* Created with IntelliJ IDEA.
* Developer: pligor
*/
abstract class MyDbPreferencesCase[INNER_TYPE, OUTER_TYPE] {
//abstract
def preferenceKey: String
def defaultValue: INNER_TYPE
def getValue(implicit context: Context): OUTER_TYPE
def setValue(newValue: OUTER_TYPE)(implicit context: Context): Boolean
//concrete
def isDefault(implicit context: Context, innerTypeManifest: Manifest[INNER_TYPE]) = {
getInnerValue.asInstanceOf[INNER_TYPE] == defaultValue
}
def isEmpty(implicit context: Context, innerTypeManifest: Manifest[INNER_TYPE]) = isDefault
private lazy val finalPreferenceKey: String = preferenceKey
def clear(implicit context: Context, innerTypeManifest: Manifest[INNER_TYPE]): Unit = {
setInnerValue(defaultValue)
}
/**
* same as clear
*/
def reset(implicit context: Context, innerTypeManifest: Manifest[INNER_TYPE]): Unit = {
clear
}
private val booleanManifest = manifest[Boolean]
private val floatManifest = manifest[Float]
private val intManifest = manifest[Int]
private val longManifest = manifest[Long]
private val stringManifest = manifest[String]
protected def getInnerValue(implicit context: Context,
innerTypeManifest: Manifest[INNER_TYPE]): Any = {
createTableIfNotExists;
val dbValue = getDBvalue
writeNullValueIfNotExists;
innerTypeManifest match {
case `booleanManifest` => dbValue.map(_.toBoolean).getOrElse(defaultValue.asInstanceOf[Boolean])
case `floatManifest` => dbValue.map(_.toFloat).getOrElse(defaultValue.asInstanceOf[Float])
case `intManifest` => dbValue.map(_.toInt).getOrElse(defaultValue.asInstanceOf[Int])
case `longManifest` => dbValue.map(_.toLong).getOrElse(defaultValue.asInstanceOf[Long])
case `stringManifest` => dbValue.getOrElse(defaultValue.asInstanceOf[String])
}
}
protected def setInnerValue(newValue: INNER_TYPE)
(implicit context: Context,
innerTypeManifest: Manifest[INNER_TYPE]): Boolean = {
createTableIfNotExists;
val stringedValue = innerTypeManifest match {
case `booleanManifest` => newValue.asInstanceOf[Boolean].toString
case `floatManifest` => newValue.asInstanceOf[Float].toString
case `intManifest` => newValue.asInstanceOf[Int].toString
case `longManifest` => newValue.asInstanceOf[Long].toString
case `stringManifest` => newValue.asInstanceOf[String]
}
val dbIdOption = getDBid
val contentValues = new ContentValues()
contentValues.put(valueColumnName, stringedValue)
if (dbIdOption.isDefined) {
DatabaseHandler.getInstance.updateById(
tableName = dbSharedPreferencesTableName,
modelId = dbIdOption.get,
contentValues = contentValues,
columnName = autoIncColumnName
)
} else {
contentValues.put(keyColumnName, finalPreferenceKey)
MySQLiteOpenHelper.validateInsertion(
DatabaseHandler.getInstance.insert(dbSharedPreferencesTableName, contentValues)
)
}
}
private val dbSharedPreferencesTableName = "android_shared_preferences"
private val autoIncColumnName = "id"
private val keyColumnName = "key"
private val valueColumnName = "value"
private def createTableIfNotExists(implicit context: Context): Unit = {
DatabaseHandler.getInstance.workWithWritableDatabase {
db =>
db.execSQL(
s"""CREATE TABLE IF NOT EXISTS $dbSharedPreferencesTableName (
|$autoIncColumnName INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|$keyColumnName TEXT NOT NULL,
|$valueColumnName TEXT NULL,
|UNIQUE ($keyColumnName)
|)""".stripMargin
)
}
}
private def getDBvalue(implicit context: Context) = {
DatabaseHandler.getInstance.getSingleScalar[String](
query = s"""SELECT "$valueColumnName" FROM $dbSharedPreferencesTableName WHERE "$keyColumnName" = ?""",
Array[String](finalPreferenceKey)
)
}
private def getDBid(implicit context: Context): Option[Long] = {
DatabaseHandler.getInstance.getSingleScalar[Long](
query = s"""SELECT $autoIncColumnName FROM $dbSharedPreferencesTableName WHERE "$keyColumnName" = ?""",
Array[String](finalPreferenceKey)
)
}
private def rowExists(implicit context: Context): Boolean = {
DatabaseHandler.getInstance.exists(
tableName = dbSharedPreferencesTableName,
value = finalPreferenceKey,
columnName = keyColumnName
)
}
private def writeNullValueIfNotExists(implicit context: Context): Unit = {
if (rowExists) {
//no need to do anything
} else {
val contentValues = new ContentValues()
contentValues.put(keyColumnName, finalPreferenceKey)
contentValues.putNull(valueColumnName)
DatabaseHandler.getInstance.insert(dbSharedPreferencesTableName, contentValues)
}
}
}
/*
TESTING ABOVE CLASS
////////////////////////////////////////////////////////////////////////////////////
{
val booleanBeforeWrite = BooleanPreference.getValue
log log s"booleanBeforeWrite: $booleanBeforeWrite"
val booleanWriteSuccess = BooleanPreference.setValue(newValue = true)
log log s"booleanWriteSuccess: $booleanWriteSuccess"
val booleanAfterWrite = BooleanPreference.getValue
log log s"booleanAfterWrite: $booleanAfterWrite"
}
{
val intImmediateWrite = IntPreference.setValue(100)
log log s"intImmediateWrite: $intImmediateWrite"
val intWrittenValue = IntPreference.getValue
log log s"intWrittenValue: $intWrittenValue"
}
{
val longImmediateWrite = LongPreference.setValue(200L)
log log s"longImmediateWrite: $longImmediateWrite"
LongPreference.clear
val longAfterClear = LongPreference.getValue
log log s"longAfterClear: $longAfterClear"
}
{
val floatBeforeWrite = FloatPreference.getValue
log log s"floatBeforeWrite: $floatBeforeWrite"
val floatWritten = FloatPreference.setValue(0.553F)
log log s"floatWritten: $floatWritten"
FloatPreference.reset
val floatAfterReset = FloatPreference.getValue
log log s"floatAfterReset: $floatAfterReset"
}
{
val stringImmediateWrite = StringPreference.setValue("go go")
log log s"stringImmediateWrite: $stringImmediateWrite"
val stringAfterWrite = StringPreference.getValue
log log s"stringAfterWrite: $stringAfterWrite"
}
////////////////////////////////////////////////////////////////////////////////////
case object BooleanPreference extends MyDbPreferencesCase[Boolean, Boolean] {
val preferenceKey: String = "BooleanPreference"
val defaultValue: Boolean = false
def getValue(implicit context: Context): Boolean = {
getInnerValue.asInstanceOf[Boolean]
}
def setValue(newValue: Boolean)(implicit context: Context): Boolean = {
setInnerValue(newValue)
}
}
case object IntPreference extends MyDbPreferencesCase[Int, Int] {
val preferenceKey: String = "IntPreference"
val defaultValue: Int = -1
def getValue(implicit context: Context): Int = {
getInnerValue.asInstanceOf[Int]
}
def setValue(newValue: Int)(implicit context: Context): Boolean = {
setInnerValue(newValue)
}
}
case object FloatPreference extends MyDbPreferencesCase[Float, Float] {
val preferenceKey: String = "FloatPreference"
val defaultValue: Float = 0.1F
def getValue(implicit context: Context): Float = {
getInnerValue.asInstanceOf[Float]
}
def setValue(newValue: Float)(implicit context: Context): Boolean = {
setInnerValue(newValue)
}
}
case object LongPreference extends MyDbPreferencesCase[Long, Long] {
val preferenceKey: String = "LongPreference"
val defaultValue: Long = +1
def getValue(implicit context: Context): Long = {
getInnerValue.asInstanceOf[Long]
}
def setValue(newValue: Long)(implicit context: Context): Boolean = {
setInnerValue(newValue)
}
}
case object StringPreference extends MyDbPreferencesCase[String, String] {
val preferenceKey: String = "StringPreference"
val defaultValue: String = "tipota"
def getValue(implicit context: Context): String = {
getInnerValue.asInstanceOf[String]
}
def setValue(newValue: String)(implicit context: Context): Boolean = {
setInnerValue(newValue)
}
}
*/
Above source code takes for granted that you have already implemented a DatabaseHandler with the necessary methods

Categories

Resources