AnnoPref — make saving SharedPreferences easier
I have been working on Android for years. Whenever I need to get/set
(or get/put
) a field in the SharedPreferences file, it feels cumbersome. Sometimes, it’s because I don’t know what name to give the preference. Additionally, my code often gets cluttered with numerous string constants, and the get/put
operations of the SharedPreferences object are tied to those constants, or I have to define numerous get/set
methods again.
These reasons motivated me to learn about the Java annotation processor and implement a shortcut to define preference field names and their corresponding get/set/has functions. By simply defining a class with fields, AnnoPref handles the rest.
Let’s look at an example: if we need to save two preferences for our user settings, one for the startup screen and another for double taps to close. Previously, I would write it like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class UserSettings{
static final String PREF_STARTUP_SCREEN = "startup-screen";
static final String PREF_DOUBLE_TAPS_TO_CLOSE = "double-tap-close";
public static int getStartupScreen(){
return PreferenceHelper.getInt(PREF_STARTUP_SCREEN, 0);
}
public static void setStartupScreen(int screen){
PreferenceHelper.set(PREF_STARTUP_SCREEN, screen);
}
public static boolean isDoubleTapsClose(){
return PreferenceHelper.getBoolean(PREF_DOUBLE_TAPS_TO_CLOSE, false);
}
public static void setDoubleTapsClose(boolean doubleTapsClose){
PreferenceHelper.set(PREF_DOUBLE_TAPS_TO_CLOSE, doubleTapsClose)
}
}
This is the version with AnnoPref:
1
2
3
4
5
@Preference
class UserSettings{
int startupScreen;
boolean doubleTapsToClose;
}
As you can see, we save 11 lines of code for these two fields. To access them, simply call:
//...
UserSettingsPref.getStartupScreen();
UserSettingsPref.setStartupScreen();
UserSettingsPref.isDoubleTapsClose();
UserSettingsPref.setDoubleTapsClose();
//...
The @Preference
annotation and its fields offer several configurations for customization and security.
The first property of the @Preference
annotation is prefix
. This string value will be applied as a prefix to each field key name in the preference file. Although we don’t need to worry about key-value levels, this helps avoid naming conflicts for fields in multiple preference classes. The default value of prefix
is an empty string (annotations do not allow null values).
@Preference(prefix="user-")
class UserSettings{
int startupScreen; // key: user-startupScreen
boolean doubleTapsClose; // key: user-doubleTapsClose
}
If you find it difficult to name a prefix, simply set autoPrefix
to true
in the @Preference
annotation. When this value is true
, the preprocessor will use the package name and class name as the prefix. The priority of autoPrefix
is lower than prefix
, so if prefix
is not empty, the prefix
value will be used regardless of the autoPrefix
setting.
package com.sample.settings
@Preference(autoPrefix=true) //the prefix will be "com.tuanchauict.settings.UserSettings"
class UserSettings{
...
}
The third property of @Preference
is antiHack
. This property hashes the keys of preference fields using MD5 and Base64. While this doesn’t provide robust protection against hacking, it does make the keys harder to read. By default, antiHack
is turned off.
The last property of @Preference
allows us to define how we access the preferences: SINGLETON
or STATIC
methods. If we set the type to SINGLETON
(enum), an instance of the preference class will be created, and a getInstance()
method will be generated. The default type is STATIC
.
There are two annotations for fields: one allows us to customize the key name of fields, and the other lets us ignore a field. To customize the field key name, we use @Field(name="custom-key")
. To ignore a field, we use @Ignore
. When a field name is set, it will be the parameter of the hash function if antiHack
is turned on.
@Preference
class UserSettings{
@Field(name="startup-screen")
int startupScreen;
@Ignore
String userSection; //userSection won't be generated to get/set functions
}
Currently, AnnoPref supports the same types as the SharedPreferences
class: boolean
, int
, long
, float
, and String
. Additionally, it can store lists of Integer
, Long
, Float
, and String
, as well as sets of these types. These lists and sets are serialized to String
before being stored in the preferences.
Check annopref repository on GitHub for more detail.