PreferenceRoom

Additional

Language
Java
Version
1.1.8 (Jun 16, 2019)
Created
Nov 20, 2017
Updated
Jul 19, 2019
Owner
Jaewoong Eum (skydoves)
Contributor
Jaewoong Eum (skydoves)
1
Activity
Badge
Generate
Download
Source code
APK file

Show card

PreferenceRoom

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 projects using this library?

GithubFollowsOpen Source

All-In-OneOpen Source

Battle ComicsProduct

Epoptia Open Source

Download

Gradle

Add below dependencies to your module's build.gradle file.

dependencies {
    implementation "com.github.skydoves:preferenceroom:1.1.8"
    annotationProcessor "com.github.skydoves:preferenceroom-processor:1.1.8"
    // kotlin project
    kapt "com.github.skydoves:preferenceroom-processor:1.1.8"
}

Table of Contents

1.PreferenceEntity

2.PreferenceComponent

3.Dependency Injection

4.Usage in Kotlin

5.Proguard-Rules

6.Debugging with Stetho

PreferenceEntity

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


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

    @KeyName("userPet")
    @TypeConverter(PetConverter.class)
    protected Pet userPetInfo;

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

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

    @PreferenceFunction("visits")
    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() {
   @Override
   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.

@DefaultPreference
@PreferenceEntity("ProfileWithDefault")
public class UserProfilewithDefaultPreference {
    @KeyName("nickname")
    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.

PreferenceManager.getDefaultSharedPreferences(context);

So we can connect with PreferenceFragmentCompat, PreferenceScreen or etc.

KeyName

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


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

TypeConverter

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



@TypeConverter(PetConverter.class)
protected Pet userPetInfo; // 'Pet' class field in an entity.

Below example is creating a converter class using Gson. Converter class should extends the 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.
PreferenceTypeConverter<?> class.


public class PetConverter extends PreferenceTypeConverter<Pet> {

    private final Gson gson;

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

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

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

BaseTypeConverter

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) {
        super(clazz);
        this.gson = new Gson();
    }

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

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

So we can use the converter to any classes.

@KeyName("userinfo")
@TypeConverter(BaseGsonConverter.class)
protected PrivateInfo privateInfo;

@KeyName("userPet")
@TypeConverter(BaseGsonConverter.class)
protected Pet userPetInfo;

PreferenceFunction

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


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

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

EncryptEntity

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


@EncryptEntity("1234567890ABCDFG")
@PreferenceEntity("UserProfile")
public class Profile {
}

Or we can customize encrypting and decrypting algorithm using PreferenceFunction.

@PreferenceFunction("uuid")
public String putUuidFunction(String uuid) {
   return SecurityUtils.encrypt(uuid);
}

@PreferenceFunction("uuid")
public String getUuidFunction(String uuid) {
    return SecurityUtils.decrypt(uuid);
}

PreferenceComponent

PreferenceComponent integrates entities. We can integrate many entities as one component using entities value in 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 annotation.
@PreferenceComponent annotation should be annotated on an interface class.


@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 {

    @Override
    public void onCreate() {
        super.onCreate();
        PreferenceComponent_UserProfileComponent.init(this);
    }
}

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.


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.

@InjectPreference
public PreferenceComponent_UserProfileComponent component;

@InjectPreference
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.

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.activity_main);
   PreferenceComponent_UserProfileComponent.getInstance().inject(this);
}

Usage in Kotlin

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


@PreferenceEntity("UserDevice")
open class Device {
    @KeyName("version")
    @JvmField val deviceVersion: String? = null

    @KeyName("uuid")
    @JvmField val userUUID: String? = null

    @PreferenceFunction("uuid")
    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.

@InjectPreference
lateinit var component: PreferenceComponent_UserProfileComponent
    
override fun onCreate(savedInstanceState: Bundle?) {
   super.onCreate(savedInstanceState)
   setContentView(R.layout.activity_main)
    PreferenceComponent_UserProfileComponent.getInstance().inject(this) // inject dependency injection to MainActivity.

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.

References

Find this library useful? ❤️

Support it by joining stargazers for this repository. ⭐️

Supports ☕️

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


License

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

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.