Thursday, July 14, 2011

Android: SharedPreferences and Generics Part 1

I love how Android has implemented a super quick and easy method of storing simple primitive key/value data... SharedPreferences

I like to use Android application resources (R class), SharedPreferences and Generics together at the application level to create a real quick and stable system of saving and retrieving simple stored data throughout an entire application.

...extends Application

First I'd like to take a minute and talk about extending the Application class. Usually this is not necessary nor is it a good idea. I believe however, that this is a good way to extend the Application class. It allows easy, consistent access to our SharedPreferences throughout the application and not just in an Activity or Service. It really shines when you create a stand alone class. Using this method you won't have to pass the context around... which can get out of hand real fast!

A little about SharedPreferences...

How do SharedPreferences work and why would I need them?

Android provides many different ways of storing and retrieving data. You could use a premade file, a file on the external storage (microSD) or you could even use the SQLLight database that is provided to you through a content provider.

These are all great and effective methods of storing data but they each come at a cost. Using files creates overhead using the file IObuffer, writing to a database can be painfully slow when writing many times in a short timespan, the list goes on.

So for example, if I have some simple data and it may or may not be written and re-written many times throughout the application's lifecycle, I don't want to have to create DB handlers, calls to content providers and queries and buffered readers and lions and tigers and blah blah blah whatever... just to manage the data.

This is where the SharedPreferences class really shines! Reading and writing data using SharedPreferences is extremely fast, secure (only YOUR application has access to the stored data) and overall easy to use. Simply put SharedPreferences are a quick and easy storage solution that has the ability to store data in key/value pairs.

Here is a simple breakdown of what you can use for the key and the value:

Key: Currently, the key is always a value of the type String.
Value: Currently SharedPreferences only supports storing the following value types:
  • String
  • Integer
  • Float
  • Long
  • Boolean

How to use SharedPreferences

To use the SharedPreference class you must first get a handle to the file.

public class SomeClass {   
   private SharedPreferences mSomePref;
         
   public SomeConstructorOrOnCreate() {
     mSomePref = this.getSharedPreferences( "pref_filename_no_extension", MODE_PRIVATE);
 }
}

Then if you want to put some data, say a string, into that preference file you would do so like this:

private void someMethod() {
  // Do some work...

  // Get a handle to the shared preferences editor
  SharedPreferences.Editor edit = mSomePref.edit();

  // do the work
  edit.putString("myKey", "My value which is the type: String");

  // commit() must ALWAYS be called after doing all the work. If it is not
  // then the changes will NOT be commited!
  edit.commit();
}

Now to retrieve that data we call like this:

  // Get the value of the preference   
  String storedPref = mSomePref.getString("myKey", "Some default value to return if this key doesn't exist"); 

AWESOME!!!
Yeah, all the way until you need to add another preference OR you need to update that one you just set...

private void anotherMethodThatChangesTheStoredPref() {   
  // ...do some work...

  // Need to change the value of my key
  SharedPreferences.Editor edit = mSomePref.edit();
  edit.putString("myKey", "The new value that will overwrite the previously stored value");
  edit.commit(); 
} 

Ah, now I see an encapsulation issue. No problem right? Separate adding the preference into its own method and pass the value in with a parameter.

private setSharedPref(final String key, final String value) {
  SharedPreferences.Editor edit = mSomePref.edit();
  edit.putString(key, value);
  edit.commit();
}

So now we can call it from various methods...

private void methodOne() {
  setSharedPref("myKey", "Some string value");
}

private void methodTwo() {
  // We can update the existing value...
  setSharedPref("myKey", "A new string value");

  // Or we can add a new entry altogether 
  setSharedPref("newKey", "Some string value");
}

This is great! However, we run into an issue when we want to store some other type of data other than strings. What if we want to store a Float or a Boolean? Well we would need to create a method for each type.

private void setStringSharedPref(final String key, final String value) {
  
  SharedPreferences.Editor edit = mSomePref.edit();
  edit.putString(key, value);
  edit.commit();
}
  
private void setFloatSharedPref(final String key, final float value) {  

  SharedPreferences.Editor edit = mSomePref.edit();
  edit.putFloat(key, value);
  edit.commit();
}
  
private void setBooleanSharedPref(final String key, final bool value) {

  SharedPreferences.Editor edit = mSomePref.edit();
  edit.putBoolean(key, value);
  edit.commit();  
}  
  // so on and so forth...  

This is ok but a lot of repetitive code, especially if you want to use your shared preferences in another class because you will have to repeat all this code in that class as well. There has to be a better way, right? Breathe easy people... there is!

Introducing Generics woot woot!!!
Since we may need to insert any one of a number of value types a generic method would be an excellent solution here. A generic method allows you to pass an unknown type using the generic type identifier (usually something like <T>) into the method and it is up to you to determine what was passed and handle it accordingly.

To do this I use a series of if/else statements and check if the class matches the type using getClass().equals(Type.class)

So with that said, our new method would look like:

private final <T> void setSharedPref(final String key, final T value) {
  
  SharedPreferences.Editor = mSomePref.edit();

  if(value.getClass().equals(String.class)) {
    edit.putString(key, (String)value); 
  } else if (value.getClass().equals(Boolean.class)) {
    edit.putBoolean(key, (Boolean)value); 
  } // So on and so forth...

  edit.commit();
}
  
This way allows you to not care what value type you are attempting to pass

So as you can see thus far the SharedPreferences class is a really fast and powerful way of managing simple data and using generics really takes them to the next level!

In the next post I will tie everything together and then demonstrate how to use all of this in a "real world" example.

1 comments:

Santosh said...

Very Useful..!!
Thankz..!!

Post a Comment