Persist your data elegantly: U2020 way

Tahsin Dane
Sep 1, 2015 · 5 min read

SharedPreferences are great and the easiest way to persist user data on Android. It lets you to store/access primitive preference data in key-value pairs. It supports storing/accessing primitive data: boolean, float, int, long, String, and StringSet (not Array unfortunately).

Almost all Android applications use it. Although usage of the API is straightforward, it has its own problems. When your codebase grows, it becomes really easy to make mistakes because it is initialized and used with String literals.

I will first talk about traditional ways to access SharedPreferences APIs and discuss the problems with all the common approaches. These problems are especially important when the codebase gets bigger.

I will then give another cleaner way to do it using dependency injection. I came across this approach in a great open-source sample app called U2020 from Jake Wharton. It uses Dagger to access SharedPreferences. We will use Dagger 2 in this post. It is the fastest dependency injection library made for Java and Android in mind.

If you already use Dagger, you will find a cleaner and safe way to access SharedPreferences. If you don’t, and want to learn Dagger 2, this is a great post for you because it shows the usefulness of Dagger by providing a real life use-case for it. I promise it doesn’t have Coffee, Heater and Pump example ☺

Traditional Ways

You first need to access an instance of a SharedPreferences class. It can be obtained in 2 different ways:

//Use a static helper method.
PreferenceManager.getDefaultSharedPreferences(Context context);
//Or use a method in your Context
getSharedPreferences(String name, int mode)

The first method gives the default SharedPreferences which saves the data to a file named as the package name of the app. With the second method, you have the option to provide the file name of the preferences file and the mode to access it.

And finally, you can get your data like below:

String myValue = prefs.getString("myKey", "deafultValue");

The problem here is that you have to provide a String key and a default value every time you use it. Most of the developers don’t use a Constants file for these keys and type them over and over again which makes it prone to errors.

Thats why I was creating a separate PrefUtils class in the past with methods like below to prevent errors:

PrefUtils.getMyValue();
PrefUtils.getMyValue(defaultValue);

Having a Utility class for accessing SharedPreferences also has its own problems. First, it gets harder to maintain it when your project gets bigger. Second, since you are using Context, there is a risk to cause a memory leak if it is not implemented properly. And I’ve seen lots of different implementations of this PrefUtils class.

Finally, when you want to store something, you will do something like this:

prefs.edit().putString("myKey", "newValue").apply();

You may realize that we are making chains of calls here to store a data. For a store operation, you have to use edit() method first (which gives you SharedPreferences.Editor object) then make your changes and finally use apply() or commit() methods. SharedPreferences.Editor object makes sure that preference values remain in a consistent state.

Although there is a lint check for it, you may forget to append apply() method at the end. And you will spend hours to find out why your data is not saved.

The U2020 Way

I really like this method and started to use it on my new projects. It protects you from making errors and makes your code cleaner. It especially goes great when you use it with Dagger.

  • Key and the default value are given in the constructor.
  • Don’t have to keep them in a separate Constants file because they are created only once.
  • Prevents accidental mistakes.
  • Beautiful and simple methods like set, get, isSet.
  • Store enums or more complex custom data.

Here is the code for a StringPreference:

public class StringPreference {
private final SharedPreferences preferences;
private final String key;
private final String defaultValue;
... public StringPreference(@NonNull SharedPreferences preferences,
@NonNull String key,
@Nullable String defaultValue) {
this.preferences = preferences;
this.key = key;
this.defaultValue = defaultValue;
}
@Nullable
public String get() {
return preferences.getString(key, defaultValue);
}
public boolean isSet() {
return preferences.contains(key);
}
public void set(@Nullable String value) {
preferences.edit().putString(key, value).apply();
}
public void delete() {
preferences.edit().remove(key).apply();
}
}

And then when you need to read/write your data, you may use it like this:

StringPreference myPref = 
new StringPreference(prefs, "myKey", "defaultValue");
String oldValue = mPref.get();
mPref.set("newValue");

One disadvantage of this is that you have to instantiate objects of this for every value you want to persist. Be patient! Dagger will come to rescue.

In Dagger, you need to have Modules where you provide your objects to be injected. Almost all Android Dagger projects have ApplicationModule where you provide Application specific objects. (eg. Application, NotificationManager, GoogleApiClient) You can find an example here.

You will probably have a DataModule in your app too. So it is better to provide SharedPreferences in it.

@Module
public final class DataModule {
@Provides @Singleton
SharedPreferences provideSharedPreferences(Application app) {
return PreferenceManager.getDefaultSharedPreferences(app);
}
...
}

Notice that we made it a Singleton. That way, provideSharedPreferences method will be called only once.

Application object will be injected here automagically by Dagger since it is provided in the AppModule. Also notice that we are using Application as a Context here to prevent memory leak problems.

Now let’s have a PrefsModule:

@Module(includes = DataModule.class)
public class PrefsModule {

...
@Provides @Singleton
StringPreference provideMyValue(SharedPreferences prefs) {
return new StringPreference(prefs, "myKey", "defaultValue");
}
}

We can provide all our preferences here to be used throughout the application. Keys and default values are only provided at once in this module.

And finally, Inject annotation is used to access it. When you do that, only 1 Singleton instance will be used.

public class MainActivity extends Activity {

@Inject StringPreference myPreference;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String value = myPreference.get(); }
}

We’ve created separate classes for every data type you want to persist. And then we have 2 Dagger Modules. That may seem to be much, but in the overall I think it is an elegant and safe way to use SharedPreferences.

Appendix

My open source Wear Pomodoro application where I use this technique: https://github.com/pomopomo/WearPomodoro/

Great tutorials and videos to learn Dagger:

Follow me on @tasomaniac and +SaidTahsinDane

Google Developers Experts

Experts on various Google products talking tech.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store