Save data in SharedPreferences when application is not running - android

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

Related

Android : SharedPreferences and MVC pattern

I'm developping my app using the MVC pattern. To store/access data, my controllers use a class named "DataStorage", and for now this class allows to store/access simple parameters about my app (username, data storage path, ...). In other words, I want to call a few methods like "getParameter(String key)" or "setParameter(String key, String value)".
I think SharedPreferences would be the most convenient way to store these parameters, so my get/setParameters use this class.
In all the examples I have seen, SharedPreferences is called from an Activity and there is no problem to call methods such as "getSharedPreferences" or "getApplicationContext"/"getContext". Because my DataStorage class is not an activity, for now I ask my first activity to give its context when creating a new DataStorage instance, and it works well to store my parameters. My problem : I want to be able to remove parameters from another activity using clear + commit methods. But it doesn't work (parameters are still there), and I think the reason is I give the 2nd activity context when creating another instance of DataStorage. The problem might be something else though, I've been practicing Android for only 2 days now...
To summarize how my app works :
Activity 1 creates a DataStorage class and provides its context to the DataStorage constructor. The DataStorage might store a parameter into a SharedPreferences file (or not...)
When I run my app again, if a particular parameter is set in the SharedPreferences file, then I start Activity 2 instead of Activity 1. Using the menu on Activity 2, I want to be able to clear the SharedPreferences file (in order to get Activity 1 again when I restart the app), so I create another DataStorage instance (and I provide Activity 2 context) and I call the method to clear all parameters.
As I said, first part works well (I can store parameters), but clear & commit do nothing to my SharedPreferences file.
I don't want to put a piece of code for this directly in my activities.
Can you help me with this ? What am I doing wrong in the way I use SharedPreferences ?
Thank you for your help !
Edit :
public class DataStorage {
private Context context;
private String settingsFilename;
private SharedPreferences settings;
public DataStorage(Context activityContext, String filename) {
context = activityContext;
settingsFilename = filename;
settings = context.getSharedPreferences(settingsFilename, Context.MODE_PRIVATE);
}
public void newSharedPreference(String key, String value) {
settings.edit().putString(key, value).apply();
settings.edit().commit();
}
public String getSharedPreference(String key) {
return settings.getString(key, null);
}
public void clearPreferences() {
settings.edit().clear();
settings.edit().commit();
Toast.makeText(context,settings.toString(), Toast.LENGTH_LONG).show();
}
}
In my first activity (the code is part of onCreate method) :
DataStorage storage = new DataStorage(this, getResources().getString(R.string.sharedPreferencesFile));
username = storage.getSharedPreference("username");
Toast.makeText(this, username, Toast.LENGTH_LONG).show();
if (username != null) {
Intent nextActivity = new Intent(this, ActivityMainMenu.class);
startActivity(nextActivity);
} else {
setContentView(R.layout.activity_name);
}
In my 2nd activity :
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
switch(id) {
case R.id.action_clearSharedPref :
storage.clearPreferences();
break;
case R.id.action_leave :
System.exit(RESULT_OK);
}
return super.onOptionsItemSelected(item);
}
(Storage is constructed exactly as I did in my first Activity)
I tried to replace "this" by getApplicationContext() in DataStorage constructor, but it didn't work.
From Editor Class Overview
Interface used for modifying values in a SharedPreferences object. All changes you make in an editor are batched, and not copied back to the original SharedPreferences until you call commit() or apply().
You need to update to change methods at your DataStorage
public void newSharedPreference(String key, String value) {
settings.edit().putString(key, value).apply();
}
and
public void clearPreferences() {
settings.edit().clear().apply();
Toast.makeText(context,settings.toString(), Toast.LENGTH_LONG).show();
}
Reason of issue is next
settings.edit().clear(); // clear is ok, but it won't be saved because
settings.edit().commit(); // create new editor and commit nothing

Where should I synchronize on this static SharedPreference helper class?

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

Store Objects in ApplicationContext

When my application goes to background , my (static and singleton) objects are cleared.
So I tried to store these objects in Applicaton Context . I am using the following code.
Accounts.create(getApplicationContext()) will be called once to store the accounts instance.
Is that possible(reliable) to store objects in Application Context ? I am not sure the following way is correct or not . please guide ..
public class Init extends Application {
private Hashtable<Object, Object> globalStore = new Hashtable<Object, Object>();
public void putToGlobalStore(Object key, Object value) {
globalStore.put(key, value);
}
public Object takeFromGlobalStore(Object key) {
return this.globalStore.get(key);
}
public void removeFromGlobalStore(Object key) {
this.globalStore.remove(key);
}
public boolean containsInGlobalStore(Object key) {
return this.globalStore.containsKey(key);
}
}
public class Accounts {
protected Accounts(String name, Context context) {
Init init = (Init) applicationContext;
init.putToGlobalStore(name, this);
}
private static Init applicationContext;
public static void create(Context context) {
if (context instanceof Application)
applicationContext = (Init) context;
else
applicationContext = (Init) context.getApplicationContext();
if (applicationContext.containsInGlobalStore(GLOBAL_NAME))
Logger.log("Warning " + GLOBAL_NAME
+ " is already created. This will remove all old datas");
new Accounts(GLOBAL_NAME, applicationContext);
}
private static final String GLOBAL_NAME = "accounts";
public static Accounts getInstance() {
try {
return (Accounts) applicationContext
.takeFromGlobalStore(GLOBAL_NAME);
} catch (Exception e) {
Logger.log("GLOBAL_NAME Lost");
return null;
}
}
Please help.
You should know that the application context itself gets destroyed if left unused for a long time in the background. So there is no guarantee that your static and singleton objects will not be cleared when the app is in background. Instead what you can do is persist your objects from time to time (either in a flat-file or shared preference or database) and restore them in the onCreate method of the Application class
I have been using this method in my application and i didn't see any problem unless my process gets killed by the OS or if there is a crash in my application and my app gets restarted.
If you think whatever data you are storing is valid for only life time of a program why don't you override OnCreate of Application object and create all your singletons there. This way you can always make sure your application has all singletons before your app starts functioning.
Application class is not permanent.
If App process killed, Application class private member variable data loss.
Using Shared Preferences.
I know this question was asked a long time ago, but here's a good article that suggests using the Application object to store data is generally not a sound design methodology.

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);
}

Passing data through intents instead of constructors

Having developed many desktop GUI apps (from Xt to Qt, Java Awt/Swt/Swing, etc) I really find it difficult to get used to Android.
Suppose I have the MainView Activity class which explicitly calls DetailedView via intent mechanism as shown next:
Since an Activity class is instantiated via onCreate() how do I
customize it? (No constructor, only
pass data through intent!)
Is there a way to get a reference for the DetailedView instance in
MainActivity?
Is there a way to get a reference for the MainActivity instance in
DetailedView?
How can I pass the reference to FrontEnd to the DetailedView class?
Intent.putExtras() allows only for
certain data types to pass to the
intent receiver class.
MainActivity {
...
FrontEnd fe;
...
public void onCreate(Bundle savedInstanceState) {
...
Intent myIntent = new Intent(this, DetailedView.class);
...
}
protected void onListItemClick(ListView l, View v, int position, long id) {
...
startActivityForResult(myIntent,..);
...
}
}
One way of passing simple data between activities/services of a specific app is to use the SharedPreferences functionality of android.
This may not be the most elegant code to get the job done, but I routinely create a static "utility" class in my Android projects to allow for 1 line get and set of simple data types via shared preferences
private static final String PREFERENCE_FILE_NAME = "com.snctln.util.test.SharedPreferencesFile";
private static final String BOOL_VALUE_ONE = "bValueOne";
public static boolean getBooleanValue1(Context context)
{
SharedPreferences prefs = context.getSharedPreferences(PREFERENCE_FILE_NAME, Context.MODE_PRIVATE);
return prefs.getBoolean(BOOL_VALUE_ONE, true); // return true if the value does not exist
}
public static void setBooleanValue1(Context context, int appWidgetId, boolean actualvalue)
{
SharedPreferences.Editor prefs = context.getSharedPreferences(PREFERENCE_FILE_NAME, Context.MODE_PRIVATE).edit();
prefs.putBoolean(BOOL_VALUE_ONE, actualvalue);
prefs.commit();
}
I frequently cheat and use static 'getInstance' calls to communicate between Activities and views. This works as long as they're both in the same proc, and I've yet to have a data access failure...but I'm sure it's only a matter of time...IF you're looking for a hacky quick fix this could be it, otherwise you have to pass data through intents.

Categories

Resources