SharedPreferences
is the most simple, quick, and efficient way of storing local data in your Android application. It's a framework that essentially allows you to store and associate various key-value pairs with your application (think of this as a map that comes with your application, which you can tap into at any time), and because each application is associated with its own SharedPreferences
class, the data that gets stored and committed persists across all user sessions. However, because of its simple and efficient nature, SharedPreferences
only allows you to save primitive data types (that is, booleans, floats, longs, ints, and strings), so keep this in mind when deciding what to store as a shared preference.
Let's look at an example of how you would access and use your application's SharedPreferences
class:
public class SharedPreferencesExample extends Activity { private static final String MY_DB = "my_db"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // INSTANTIATE SHARED PREFERENCES CLASS SharedPreferences sp = getSharedPreferences(MY_DB, Context.MODE_PRIVATE); // LOAD THE EDITOR – REMEMBER TO COMMIT CHANGES! Editor e = sp.edit(); e.putString("strKey", "Hello World"); e.putBoolean("boolKey", true); e.commit(); String stringValue = sp.getString("strKey", "error"); boolean booleanValue = sp.getBoolean("boolKey", false); Log.i("LOG_TAG", "String value: " + stringValue); Log.i("LOG_TAG ", "Boolean value: " + booleanValue); } }
Let's walk through what's going on in this little code snippet. First we start an Activity
and in the
onCreate()
method, we make a request to retrieve a SharedPreferences
class. The arguments for the
getSharedPreferences()
method are:
getSharedPreferences(String mapName, int mapMode)
Here the first argument simply specifies which shared preference mapping you want (each application can own several separate shared preference mappings, and so, just like you would specify the table name in a database, you must specify which mapping you want to retrieve). The second argument is a little more complex — in the example above, we pass in MODE_PRIVATE
as the argument and this argument simply specifies the visibility of the shared preference instance you are retrieving (in this case the visibility is set to private, so that only your application can access the mappings contents). Other modes include:
MODE_WORLD_READABLE
: Makes the visibility of your map accessible by other applications, though contents can only be readMODE_WORD_WRITEABLE
: Makes the visibility of your map accessible by other applications for both reading and writingMODE_MULTI_PROCESS
: This mode, available since API Level 11, allows you to modify your map by multiple processes which may be writing to the same shared preference instance
Now, once we have our shared preference object, we can immediately start retrieving contents by its various
get()
methods — for instance, the getString()
and
getBoolean()
methods we saw earlier. These get()
methods will typically take two parameters: the first being the key, and the second being the default value if the given key is not found. Taking the previous example, we have:
String stringValue = sp.getString("strKey", "error"); boolean booleanValue = sp.getBoolean("boolKey", false);
And so, in the first case, we're trying to retrieve the string value associated with the key strKey
, and defaulting to the string error
if no such key is found. Likewise, in the second case, we're trying to retrieve a boolean value associated with the key boolKey
, and defaulting to the boolean false
if no such key is found.
However, if you want to edit contents or add new content, then you'll have to retrieve the Editor
object that each shared preference instance contains. This Editor
object contains all of the put()
methods which allow you to pass a key along with its associated value (just like you would for a standard Map
object) — the only caveat is that after you add or update the content of your shared preference, you need to call the Editor
object's
commit()
method to save down those changes. Furthermore, again, just like a standard Map
object, the Editor
class also contains remove()
and
clear()
methods for you to freely manipulate the contents of your shared preference.
One last thing to note before we move on to typical use cases of SharedPreferences
is that if you decide to set the visibility of your shared preference instance to MODE_WORLD_WRITEABLE
, then you are potentially exposing yourself to various security breaches by malicious external applications. As a result, in practice, this mode is not recommended. However, the desire to share information locally between two applications is still one that many developers face, and so a method for doing so was developed that simply involves setting an android:sharedUserId
in your application's manifest files.
How this works is that each application, when signed and exported, is given an auto-generated application ID. However, if you explicitly set this ID in the application's manifest file, then, assuming two applications are signed with the same key, they will be able to freely access each other's data without having to expose their data to the rest of the applications on a user's phone. In other words, by setting the same ID for two applications, those two and only those two applications will be able to accss each other's data.
Now that we know how to instantiate and edit a shared preference object, it's important to think about some typical use cases for this type of data storage. And so, following are a couple of examples, illustrating what kinds of small, primitive key-value data pairs applications tend to like to save.
For many applications, if this is the user's first visit, then they will want to display some kind of instructions/tutorials activity or a splash screen activity:
public class SharedPreferencesExample2 extends Activity { private static final String MY_DB = "my_db"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); SharedPreferences sp = getSharedPreferences(MY_DB, Context.MODE_PRIVATE); /** * CHECK IF THIS IS USER'S FIRST VISIT */ boolean hasVisited = sp.getBoolean("hasVisited", false); if (!hasVisited) { // ... // SHOW SPLASH ACTIVITY, LOGIN ACTIVITY, ETC // ... // DON'T FORGET TO COMMIT THE CHANGE! Editor e = sp.edit(); e.putBoolean("hasVisited", true); e.commit(); } } }
Many applications will have some kind of caching, or syncing, feature built-in, which will require regular updating. By saving the last update time, we can quickly check to see how much time has elapsed, and decide whether or not an update/sync needs to occur:
Tip
Downloading the example code
You can download the example code files for all Packt books you have purchased from your account at http://www.packtpub.com. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you.
/** * CHECK LAST UPDATE TIME */ long lastUpdateTime = sp.getLong("lastUpdateKey", 0L); long timeElapsed = System.currentTimeMillis() - lastUpdateTime; // YOUR UPDATE FREQUENCY HERE final long UPDATE_FREQ = 1000 * 60 * 60 * 24; if (timeElapsed > UPDATE_FREQ) { // ... // PERFORM NECESSARY UPDATES // ... } // STORE LATEST UPDATE TIME Editor e = sp.edit(); e.putLong("lastUpdateKey", System.currentTimeMillis()); e.commit();
Many applications will allow the user to remember their username (as well as other login-oriented fields such as PINs, phone numbers, and so on) and a shared preference is a great way to store a simple primitive string ID:
/** * CACHE USER NAME AS STRING */ // TYPICALLY YOU WILL HAVE AN EDIT TEXT VIEW // WHERE THE USER ENTERS THEIR USERNAME EditText userNameLoginText = (EditText) findViewById(R.id.login_editText); String userName = userNameLoginText.getText().toString(); Editor e = sp.edit(); e.putString("userNameCache", userName); e.commit();
For many applications, the functionality of the application will change depending on the application's state, typically set by the user. Consider a phone ringer application — if the user specifies that no functionality should occur if the phone is in silent mode, then this is probably an important state to remember:
/** * REMEBERING A CERTAIN STATE */ boolean isSilentMode = sp.getBoolean("isSilentRinger", false); if (isSilentMode) { // ... // TURN OFF APPLICATION // ... }
Any location-based application will often want to cache the user's last location for a number of reasons (perhaps the user has turned off GPS, or has a weak signal, and so on). This can be easily done by converting the latitude and longitude of the user to floats and then storing those floats in a shared preference instance:
/** * CACHING A LOCATION */ // INSTANTIATE LOCATION MANAGER LocationManager locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE); // ... // IGNORE LOCATION LISTENERS FOR NOW // ... Location lastKnownLocation = locationManager.getLastKnownLocation (LocationManager.NETWORK_PROVIDER); float lat = (float) lastKnownLocation.getLatitude(); float lon = (float) lastKnownLocation.getLongitude(); Editor e = sp.edit(); e.putFloat("latitudeCache", lat); e.putFloat("longitudeCache", lon); e.commit();
With the latest version of Android (API Level 11), there is also a new getStringSet()
method which allows you to set and retrieve a set of string objects for a given associated key. Here's how it looks in action:
Set<String> values = new HashSet<String>(); values.add("Hello"); values.add("World"); Editor e = sp.edit(); e.putStringSet("strSetKey", values); e.commit(); Set<String> ret = sp.getStringSet(values, new HashSet<String>()); for(String r : ret) { Log.i("SharedPreferencesExample", "Retrieved vals: " + r); }
Use cases for this are plenty — but for now let's move on.