Where should I synchronize on this static SharedPreference helper class? - android

I have created a class holding SharedPreferences access in a static manner. Looking at the AOSP ContextImpl.java's SharedPreferenceImpl, I see that synchronized(this) is used when put and get are executed.
Should I still add synchronized somewhere in my code below?
public class AppPreferences {
// Get static SharedPreferences Editor
private static Editor getEditor(Context ctx) {
return PreferenceManager.getDefaultSharedPreferences(ctx).edit();
}
// Get static SharedPreferences
private static SharedPreferences getPref(Context ctx) {
return PreferenceManager.getDefaultSharedPreferences(ctx);
}
public static String getUserName(Context ctx, String defaul) {
return getPref(ctx).getString("user_name", defaul);
}
public static void setUserName(Context ctx, String text) {
getEditor(ctx).putString("user_name", text).commit();
}
}

In android.app.ContextImpl there is a static field
private static final HashMap<String, SharedPreferencesImpl> sSharedPrefs =
new HashMap<String, SharedPreferencesImpl>();
(aside private static final HashMap ?! /aside).
This is populated here. So all threads in an application sharing the same context (I have asked here but I am still not quite 100% certain) will share this static map of SharedPreferencesImpl instances - Now whenever you call edit() you get a new EditorImpl instance - so in the "synchronized(this)" you refer to in your question the this refers to the instance of EditorImpl at hand - which does not do much - it just synchronizes access to the internal map of the EditorImpl. But the (different) editors synchronize on the (common) SharedPreferencesImpl instance when they are about to modify this (SharedPreferencesImpl) instance. So in commit() for instance commitToMemory() is called where the synchronization is on SharedPreferencesImpl.this. Keep in mind though that the writes to disk are enqueued in random order (see the javadoc for enqueueDiskWriteSo and notice in commit that no lock is held between writing to memory and enqueing for write to disk). So you should be safe modifying the preferences as long as you do not depend on order of modifications and do not depend on atomically checking and setting a preference value (which needs synchronizing of your own)
NB the code I quote is for 2.3.1_r1 - hopefully still valid

Related

Updating an object stored in one Activity from another activity

I am creating an object called "AppEngine" inside my first activity. This AppEngine object stores and arrayList of Events, and begins with 2 events inside it.
From the first Activity I click a button which takes me to a second Activity in which I add an event object to the arrayList by using.
appEngine.getList.add(new Event)
When inside Activity 2, If I am to call appEngine.getList.size() the size is correctly returned as 3 and I can see the extra event.
When I switch back to Activity 2, I am calling appEngine.getList.size()however it only returns 2, and the extra event is not in there. How can i get the appEngine object to update?
save your array list in shared preference like this create a AppPreference Class:-
public class AppPreference {
private static SharedPreferences mPrefs;
private static SharedPreferences.Editor mPrefsEditor;
public static Set<AppEngine> getList(Context ctx) {
mPrefs = PreferenceManager.getDefaultSharedPreferences(ctx);
return mPrefs.getStringSet("AppEngineList", null);
}
public static void setList(Context ctx, ArrayList<AppEngine> value) {
mPrefs = PreferenceManager.getDefaultSharedPreferences(ctx);
mPrefsEditor = mPrefs.edit();
Set<String> set = new HashSet<>();
set.addAll(value);
mPrefsEditor.putStringSet("AppEngineList", set);
mPrefsEditor.commit();
}
}
set your value from first activity like this:-
setList(YourActivity.class, list);
and get your list from anywhere in you app:-
ArrayLis<AppEngine> list = AppPreference.getList(yourActivity.class);
If you only want the appEnginge object to persist during a single app session and not persist trough a complete app close/restart, then you should use a handler class.
EngineHandler.java:
public static class engineHandler {
public static appEnginge _appEngine;
}
and then just call
engineHandler._appEngine = _myAppengine;
engineHandler._appEngine.getList().add(new Event);
from your activity(s). The engineHandler will be accessible from any activity in your application.
You can use Singleton design Pattern.
You create one object from AppEnginRepository with eventList field and in your app you just get it and each time you want, you change it.
public class AppEnginRepository {
private List<Event> eventList;
private static final AppEnginRepository ourInstance = new AppEnginRepository();
public static AppEnginRepository getInstance() {
return ourInstance;
}
private AppEnginRepository() {
eventList = new ArrayList<>();
}
public List<Event> getEventList() {
return eventList;
}
public void setEventList(List<Event> eventList) {
this.eventList = eventList;
}
}
In your Activities
AppEnginRepository enginRepository=AppEnginRepository.getInstance();
List<Event> eventList=enginRepository.getEventList();
eventList.add(new Event());
int eventListSize=eventList.size();
It would be good to think each Activity is totally separated execution. Technically it's arguable, but it is good assumption to design cleaner and safer software.
Given assumption, there are several approaches to maintain data across Activities in an app.
As #Sandeep Malik's answer, use SharedPreference OS-given storage.
Similar to #Joachim Haglund's answer, use Singleton pattern to maintain an object in app.
Or, use small database like Realm.
Every approach has similar fashion; there should be an isolated and independent storage which is not belonged to one of Activity but belonged to ApplicationContext or underlying framework.

Shared Preferences reset data when app crashed. Please guide

My app got crashed and all the data in shared preference got cleared.
I am saving some flags and maintaining user session in shared preference.
One of the flag is IsFirstLaunch, which tells me whether app is launching for first time or not, if returns true then I am downloading some data from server and storing in SQLite database.
Please guide, thanks in advance.
So after the crash when it went to load the Preferences there was a blank in the preferences xml file which caused the preferences to reset.
To avoid this you could put all preference modifications in synchronized blocks or even use one synchronized static method for all preference writing.
I think - you need a better way of managing and storing the data you're saving.
The next time the shared preferences were accessed however, the xml file was cleared and started new.
for example :
private static final class SharedPreferencesImpl implements SharedPreferences {
...
public String getString(String key, String defValue) {
synchronized (this) {
String v = (String)mMap.get(key);
return v != null ? v : defValue;
}
}
...
public final class EditorImpl implements Editor {
public Editor putString(String key, String value) {
synchronized (this) {
mModified.put(key, value);
return this;
}
}
...
}
}

Save data in SharedPreferences when application is not running

I am using SharedPreferences to store my data. When my application is running data is been saved to SharedPreferences successfully but when i close my application and try to save data in it via Intent Service nothing happened . no data saved to it :
public class TinyDB {
private SharedPreferences preferences;
private String DEFAULT_APP_IMAGEDATA_DIRECTORY;
private String lastImagePath = "";
public TinyDB(Context appContext) {
preferences = PreferenceManager.getDefaultSharedPreferences(appContext);
}
public void putString(String key, String value) {
checkForNullKey(key); checkForNullValue(value);
preferences.edit().putString(key, value).apply();
}
}
I am using its object in onMessageReceive()
public void onMessageReceived(RemoteMessage remoteMessage) {
tinyDb.putString("key","value");
}
The main point that i want to make sure that i want to save value when app is not running. When app is running everything is fine.
I also want to know what class or Activity is best for initializing the object of TinyDB , and i should make it static or not ?
You can instantiate a new instance of TinyDB in your IntentService class with the Context of your Service (assuming that the IntentService is running on the same process as the original Activity). Please note that after performing all its logic, an IntentService is destroyed with all its resource, so the new TinyDB will be destroyed as well.
try
public void onMessageReceived(RemoteMessage remoteMessage) {
// shortcut and valid local variable
Context ctx = getApplicationContext()
// get instance of shared preferences using service/receiver context!
// keep in mind device protected context / boot aware stuff
// https://source.android.com/security/encryption/file-based
SharedPreferences sp = ctx.getSharedPreferences(preferencesFileName,Context.MODE_PRIVATE);
// save pref value
sp.edit().put(...).commit();
}
*preferenceFileName - if default is your_packageName + _preferences
ps some things to consider:
see man: apply() vs commit()
check how you register your receiver
read in dosc / specs / www if you are allowed to save preferences outside main thread (ActivityThread)
read Are Android's BroadcastReceivers started in a new thread?
more hints:
do not store Context class objects - as they are and should stay short lived - instead use WeakReference
// create if you want to hold context reference
WeakReference<Context> wctx = new WeakReference<Context>(context)
// then get when you want to use context
Context ctx = wctx.get();
if(ctx!=null) // context still alive we can use it

android new activity can't load singleton

I am trying to use a Singleton to share a large data object between Activities. But when I open the new Activity, the singleton comes up as empty. It seems to me that the Singleton should be the same no matter where in the Application I call if from.
It seems like the Scope of the Singleton is being limited to the individual Activity. Working around this is making my App very complicated. I must be doing something wrong. I even tried instantiating them in an extended Application class... Google says I should not have to use that though...
Can someone please point out where I am going wrong? i.e. Why does this singletom not contain the same data in each Activity?
I call it from an Activity with...
DataLog dataLog = DataLog.getInstance(this);
I have...
public class DataLog extends ArrayList<String> implements Serializable {
private static final long serialVersionUID = 0L;
private static DataLog sInstance;
private static Context mContext;
public static DataLog getInstance(Context context) {
mContext = context.getApplicationContext();
prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
if (sInstance == null) {
sInstance = new DataLog();
}
return sInstance;
}
private DataLog() {
}
public boolean add(String entry) {
super.add(entry);
return true;
}
public void add(int index, String entry) {
if (index > 0)
super.add(index, entry);
else
super.add(entry);
}
public void clear() {
super.clear();
}
...
}
Its highly advisable to avoid singleton for sharing large data sets in android.
Singletons are used for short life-cycle objects.
Switch to SharedPrefferences, SQLite DB's or file storing. You are not the only to have experienced this behavior, and the reason lies in the nature of android Activities and the system itself(managing activities and its data).
Here is an example why singleton is bad for your case:
You stored important data in it. The user knows that he can close the app on home button to call someone or whatever)maybe someone called him when he was in your app), and that when he opens your app he will come back at the same place with everything in order. (this is expected behavior from users and android apps). The system can easily kill your process and all static variables in it for memory maintenance, app inactivity etc...result=data lost. Thus its not safe to use it.

Accessing SharedPreferences through static methods

I have some information stored as SharedPreferences. I need to access that information from outsite an Activity (in from a domain model class). So I created a static method in an Activity which I only use to get the shared preferences.
This is giving me some problems, since apparently it is not possible to call the method "getSharedPreferences" from a static method.
Here's the message eclipse is giving me:
Cannot make a static reference to the non-static method
getSharedPreferences(String, int) from the type ContextWrapper
I tried to work around this by using an Activity instance, like this:
public static SharedPreferences getSharedPreferences () {
Activity act = new Activity();
return act.getSharedPreferences("FILE", 0);
}
This code gives a null point exception.
Is there a work-around? Am I going into an android-code-smell by trying to do this?
Thanks in advance.
Cristian's answer is good, but if you want to be able to access your shared preferences from everywhere the right way would be:
Create a subclass of Application, e.g. public class MyApp extends Application {...
Set the android:name attribute of your <application> tag in the AndroidManifest.xml to point to your new class, e.g. android:name="MyApp" (so the class is recognized by Android)
In the onCreate() method of your app instance, save your context (e.g. this) to a static field named app and create a static method that returns this field, e.g. getApp(). You then can use this method later to get a context of your application and therefore get your shared preferences. :-)
That's because in this case, act is an object that you just create. You have to let Android do that for you; getSharedPreferences() is a method of Context, (Activity, Service and other classes extends from Context). So, you have to make your choice:
If the method is inside an activity or other kind of context:
getApplicationContext().getSharedPreferences("foo", 0);
If the method is outside an activity or other kind of context:
// you have to pass the context to it. In your case:
// this is inside a public class
public static SharedPreferences getSharedPreferences (Context ctxt) {
return ctxt.getSharedPreferences("FILE", 0);
}
// and, this is in your activity
YourClass.this.getSharedPreferences(YourClass.this.getApplicationContext());
I had a similar problem and I solved it by simply passing the current context to the static function:
public static void LoadData(Context context)
{
SharedPreferences SaveData = context.getSharedPreferences(FILENAME, MODE_PRIVATE);
Variable = SaveData.getInt("Variable", 0);
Variable1 = SaveData.getInt("Variable1", 0);
Variable2 = SaveData.getInt("Variable2", 0);
}
Since you are calling from outside of an activity, you'll need to save the context:
public static Context context;
And inside OnCreate:
context = this;
Storing the context as a static variable, can cause problems because when the class is destroyed so are the static variables. This sometimes happens when the app is interrupted and becomes low on memory. Just make sure that the context is always set before you attempt to use it even when the class setting the context is randomly destroyed.
Here's a better alternative to storing your shared preferences in static fields.
Similar to what has been suggested here, create a class that extends Application
Make the constructor for your class take Context as a parameter.
Use your context to get shared preferences and store them in private variables.
Create public variables to return the retrieved data.
e.g
public class UserInfo extends Application{
private String SAVED_USERID;
private String SAVED_USERNAME;
public UserInfo(Context context) {
SharedPreferences prefs = context.getSharedPreferences(FILE, MODE_PRIVATE);
SAVED_USERNAME = prefs.getString("UserName", null);
SAVED_USERID = prefs.getString("UserID", null);
}
public String getSavedUserName() {
return SAVED_USERNAME;
}
public String getSavedUserID() {
return SAVED_USERID;
}
}
usage in your activity
UserInfo user = new UserInfo(this.getApplicationContext());
String SAVED_USERNAME = user.getSavedUserName();
String SAVED_USERID = user.getSavedUserID();
I had the same need - some of my preferences need to be accessed often, and efficiently. I also imagine that reading and writing a string from SharedPreferences is slightly slower than getting and setting a static variable (but likely to an insignificant degree). I also just kind of got used to using static fields, retrieving Preference values only at startup, and saving them on close.
I didn't love my options for keeping static references to the SharedPreferences/contexts directly, but so far this workaround has sufficed.
My solution:
Create a Settings class with all the static variables you need.
When the application initializes, retrieve SharedPreferences fields and immediately set all Settings fields (I call a "loadSharedPrefs()" method at the end of MainActivity's onCreate method).
In the SettingsActivity's preferenceChangeListener's initialization, set the appropriate static field in the Settings class. (I call a "setAppropriateSetting(key, value)" method at the beginning of SettingsActivity's onPreferenceChange()).
Use your static preferences wherever, whenever!
public static String getPreferenceValue(Context context) {
SharedPreferences sharedPreferences =
PreferenceManager.getDefaultSharedPreferences(context);
String key = context.getString(R.string.pref_key);
String defaultVal = context.getString(R.string.pref_default);
return sharedPreferences.getString(key,defaulVal);
}

Categories

Resources