Persist your data elegantly: U2020 way
By: Tahsin Dane
Tags: Android App Development, Dependency Injection, Android

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.

The advantages are:

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.

How to use these classes with Dagger 2?

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: