1.2.2 (Aug 14, 2021)
Nov 20, 2017
Aug 14, 2021
Jaewoong Eum (skydoves)
Jaewoong Eum (skydoves)
Source code
APK file



Android processor library for managing SharedPreferences persistence efficiently and structurally.
PreferenceRoom is inspired by Architecture Components Room Persistence and dagger.
PreferenceRoom integrates scattered SharedPreferences as an entity.
It supports setter & getter custom functions with security algorithm and could put & get objects. Also supports simple preference dependency injection with free from reflection. Fully supported in kotlin project.

What companies and projects are using this library?

Open Source
Open Source
Battle Comics
Open Source



Add below codes to your root build.gradle file (not your module build.gradle file).

allprojects {
    repositories {

And add a dependency code to your module's build.gradle file.

dependencies {
    implementation "com.github.skydoves:preferenceroom:1.2.2"
    annotationProcessor "com.github.skydoves:preferenceroom-processor:1.2.2"
    // in kotlin project use kapt instead of annotationProcessor
    kapt "com.github.skydoves:preferenceroom-processor:1.2.2"

Table of Contents



3.Dependency Injection (Use with dagger)

4.Usage in Kotlin (Incremental annotation processing)


6.Debugging with Stetho


@PreferenceEntity annotation makes SharedPreferences data as an entity.
Value in @PreferenceEntity determines the entity name.
Entity's default name is determined by class name.

public class Profile {
    protected final boolean login = false;
    @KeyName("nickname") protected final String userNickName = null;
    @KeyName("visits") protected final int visitCount = 1;

    protected Pet userPetInfo;

    public String putUserNickFunction(String nickname) {
        return "Hello, " + nickname;

    public String getUserNickFunction(String nickname) {
        return nickname + "!!!";

    public int putVisitCountFunction(int count) {
        return ++count;

After the build your project, Preference_(entity name) class will be generated automatically.

Preference_UserProfile userProfile = Preference_UserProfile.getInstance(this); // gets instance of the UserProfile entity.
userProfile.putNickname("my nickname"); // puts a value in NickName.
userProfile.getNickname(); // gets a nickname value.
userProfile.containsNickname(); // checks nickname key value is exist or not in entity.
userProfile.removeNickname(); // removes nickname key value in entity.
userProfile.nicknameKeyName(); // returns nickname key name.
userProfile.getEntityName(); // returns UserProfile entity name;
userProfile.getkeyNameList(); // returns UserProfile entity's key name lists.

// or invoke static.
Preference_UserProfile.getInstance(this).putNickname("my nickname");

we can listen the changed value using OnChangedListener.
onChanged method will be invoked if we change the value using put method.

userProfile.addNicknameOnChangedListener(new Preference_UserProfile.NicknameOnChangedListener() {
   public void onChanged(String nickname) {
     Toast.makeText(getBaseContext(), "onChanged :" + nickname, Toast.LENGTH_SHORT).show();

Auto-generated code is managed by singletons.
But we can manage more efficiently using PreferenceComponent and Dependency Injection.

We can set SharedPreference to an entity as DefaultSharedPreferences using @DefaultPreference annotation like below.

public class UserProfilewithDefaultPreference {
    protected final String userNickName = "skydoves";
    // key name will be 'Login'. (login's camel uppercase)
    protected final boolean login = false;

The ProfileWithDefault entity from the example, will be initialized like below on PreferenceRoom processor.


So we can connect with PreferenceFragmentCompat, PreferenceScreen or etc.


@KeyName annotation can be used on an entity class's field.
The field's key name will be decided by field name, but we can customize the name as taste.

@KeyName("visits") // keyname will be Visits.
protected final int visitCount = 1;


SharedPreference persists only primitive type data.
But PreferenceRoom supports persistence obejct data using TypeConverter annotation.
@TypeConverter annotation should be annotated over an object field like below.

protected Pet userPetInfo; // 'Pet' class field in an entity.

Below example is creating a converter class using Gson.
Converter class should extends the PreferenceTypeConverter<?> class.
The basic principle is making object class to string data for persistence and getting the string data and recover.
convertObject performs how to change class data to a string, convertType performs how to recover class data from the string data.

public class PetConverter extends PreferenceTypeConverter<Pet> {

    private final Gson gson;

    // default constructor will be called by PreferenceRoom
    public PetConverter() {
        this.gson = new Gson();

    public String convertObject(Pet pet) {
        return gson.toJson(pet);

    public Pet convertType(String string) {
        return gson.fromJson(string, Pet.class);


We can generalize the converter using generic like below.

public class BaseGsonConverter<T> extends PreferenceTypeConverter<T> {

    private final Gson gson;

    public BaseGsonConverter(Class<T> clazz) {
        this.gson = new Gson();

    public String convertObject(T object) {
        return gson.toJson(object);

    public T convertType(String string) {
        return gson.fromJson(string, clazz);

So we can use the converter to any classes.

protected PrivateInfo privateInfo;

protected Pet userPetInfo;


@PreferenceFunction annotation processes pre/post-processing through getter and setter functions.
@PreferenceFunction annotation's value decides a target. The target should be a keyName.
The function's naming convention is which should start with put or get prefix.
put_functionname_ is pre-processing getter function and get_functionname_ is postprocessing getter function.

public String putUserNickFunction(String nickname) {
    return "Hello, " + nickname;

public String getUserNickFunction(String nickname) {
    return nickname + "!!!";


SharedPreferences data are not safe from hacking even if private-mode.
There is a simple way to encrypt whole entity using @EncryptEntity annotation.
It is based on AES128 encryption. So we should set the key value along 16 size length.
If put method invoked, the value is encrypted automatically.
And if get method invoked, it returns the decrypted value.

public class Profile {

Or we can customize encrypting and decrypting algorithm using PreferenceFunction.

public String putUuidFunction(String uuid) {
   return SecurityUtils.encrypt(uuid);

public String getUuidFunction(String uuid) {
    return SecurityUtils.decrypt(uuid);


PreferenceComponent integrates entities. @PreferenceComponent annotation should be annotated on an interface class.
We can integrate many entities as one component using entities value in @PreferenceComponent annotation.
So we can initialize many entities at once through the component and get instances from the component.
And the instance of the PreferenceComponent is managed by singleton by PreferenceRoom. `PreferenceComponent's instance also singletons. And all entities instances are initialized when the component is initialized.

@PreferenceComponent(entities = {Profile.class, Device.class})
public interface UserProfileComponent {

After build your project, PreferenceComponent_(component's name) class will be generated automatically.
The best-recommanded way to initialize component is initializing on Application class. So we can manage the component as a singleton instance.

public class MyApplication extends Application {

    public void onCreate() {

After initializing the component, we can access and use the instances of component and component's entities anywhere.

PreferenceComponent_UserProfileComponent component = PreferenceComponent_UserProfileComponent.getInstance();
Preference_UserProfile userProfile = component.UserProfile();
Preference_UserDevice userDevice = PreferenceComponent_UserProfileComponent.getInstance().UserDevice();

Dependency Injection

PreferenceRoom supports simple dependency injection process with free from reflection using @InjectPreference annotation. But If you want to use with dagger, check this reference.

Firstly we should declare some target classes which to be injected preference instances in PreferenceComponent.

@PreferenceComponent(entities = {Profile.class, Device.class})
public interface UserProfileComponent {
    // declares targets for the dependency injection.
    void inject(MainActivity __);
    void inject(LoginActivity __);

We should annotate InjectPreference annotation to preference entity or component class field which generated by PreferenceRoom.
The field's modifier should be public.

public PreferenceComponent_UserProfileComponent component;

public Preference_UserProfile userProfile;

And the last, we should inject instances of entity and component to targets fields using inject method from an instance of the component.

protected void onCreate(@Nullable Bundle savedInstanceState) {

Usage in Kotlin

It is similar to java project usage.
But the most important thing is which we should use an open modifier on entity classes and PreferenceFunctions.
And the field's modifier should be @JvmField val.

open class Device {
    @JvmField val deviceVersion: String? = null

    @JvmField val userUUID: String? = null

    open fun putUuidFunction(uuid: String?): String? {
        return SecurityUtils.encrypt(uuid)

    @PreferenceFunction( "uuid")
    open fun getUuidFunction(uuid: String?): String? {
        return SecurityUtils.decrypt(uuid)

And the component usage is almost the same as Java examples.

@PreferenceComponent(entities = [Profile::class, Device::class])
interface UserProfileComponent {
    // declare dependency injection targets.
    fun inject(target: MainActivity)
    fun inject(target: LoginActivity)

And the last, the usage of the dependency injection is the same as the java. but we should declare the component and entity field's modifier as lateinit var.

lateinit var component: PreferenceComponent_UserProfileComponent
override fun onCreate(savedInstanceState: Bundle?) {
    PreferenceComponent_UserProfileComponent.getInstance().inject(this) // inject dependency injection to MainActivity.

Incremetal annotation processing

Starting from version 1.3.30, kapt supports incremental annotation processing as an experimental feature. Currently, annotation processing can be incremental only if all annotation processors being used are incremental.
Incremental annotation processing is enabled by default starting from version 1.3.50.
PreferenceRoom supports incremental annotation processing since version 1.1.9 and here is a Before/After example result of the enabled.

Before (23.758s) After (18.779s)

Non Existent Type Correction

If you encounter NonExistentClass error at compile time, you should add below codes on your build.gradle.
Default, Kapt replaces every unknown type (including types for the generated classes) to NonExistentClass, but you can change this behavior. Add the additional flag to the build.gradle file to enable error type inferring in stubs:

kapt {
  correctErrorTypes = true

Proguard Rules

# Retain generated class which implement PreferenceRoomImpl.
-keep public class ** implements com.skydoves.preferenceroom.PreferenceRoomImpl

# Prevent obfuscation of types which use PreferenceRoom annotations since the simple name
# is used to reflectively look up the generated Injector.
-keep class com.skydoves.preferenceroom.*
-keepclasseswithmembernames class * { @com.skydoves.preferenceroom.* <methods>; }
-keepclasseswithmembernames class * { @com.skydoves.preferenceroom.* <fields>; }

Debugging with Stetho

We can debug SharedPreferences values which managed by PreferenceRoom using Stetho.


Find this library useful? ❤️

Support it by joining stargazers for this repository.


If you feel like to sponsor me a coffee for my efforts, I would greatly appreciate it.


Copyright 2017 skydoves

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
See the License for the specific language governing permissions and
limitations under the License.