dkunzler / esperandro Goto Github PK
View Code? Open in Web Editor NEWEasy SharedPreference Engine foR ANDROid
License: Other
Easy SharedPreference Engine foR ANDROid
License: Other
Every Serializable Object could be converted to JSON and therefore transparently be put into a String preference.
Generate a full-fledged usage example.
Introduce serializer for Jackson additionally to gson serializer.
It's now possible to generate string resources for each preference. Can you extend the generated class with following function:
// prefID... one of the generated string resources via @GenerateStringResources
public <T> T getValue(Context context, int prefId);
public <T> boolean setValue(Context context, int prefId, T value);
Those functions should use the cache if enabled as well. My settings view library is finished now and with this extension I could use your preferences perfectly with it.
Implementation could look like following:
// 1) Map all existing ids to a class
private HashMap<Integer, Class> mPreferenceMap = new HashMap();
mPreferenceMap.put(R.string.<PREFERENCE>, Boolean.class);
// ...
// 2) create getter
public <T> T getValue(Context context, int id) {
Class clazz = mPreferenceMap.get(id);
if (clazz == null) {
throw new RuntimeException("Please only use string resource ids generated via @GenerateStringResources");
}
String stringId = context.getString(id);
Object __result = cache.get(stringId);
if (__result == null) {
if (clazz.equals(Boolean.class)) {
__result = preferences.getBoolean(stringId, false);
} else {
// handle other existing preference classes as wll
}
if (__result != null) {
cache.put(stringId, __result);
}
}
return __result;
}
// 3) and setter
public <T> boolean setValue(Context context, int id, T value) {
Class clazz = mPreferenceMap.get(id);
if (clazz == null) {
throw new RuntimeException("Please only use string resource ids generated via @GenerateStringResources");
}
String stringId = context.getString(id);
cache.put(stringId, value);
if (clazz.equals(Boolean.class)) {
return preferences.edit().putBoolean(stringId, value).commit();
} else {
// handle other existing preference classes as wll
}
}
With the integration of apply for issue #11 the minimum required API level was implicitly bumped to 9.
Generate an API level check into the generated class and call commit if API below 9.
Let the serializer be completely in charge of serialization by letting all objects be eligible for serialization.
Background: objects can perhaps be serialized without having the serializable interface.
The project works perfect with android-apt.
So, the instructions for Gradle can be much simpler:
dependencies {
apt 'de.devland.esperandro:esperandro:1.1'
compile 'de.devland.esperandro:esperandro-api:1.1'
}
Depending on what code is being generated some imports are not neccessary and produce warnings.
The same applies to "suppress warnings unchecked", completely against the reason the suppress statement are being generated.
We should add switches when generating the code to check if those lines are neccessary, or not.
I have a try to use Serializable object to SharePreference but throws a exception
Tried to use a serialized Object in preferences but no serializer is present
java.lang.IllegalStateException: Tried to use a serialized Object in preferences but no serializer is present
at de.devland.esperandro.Esperandro.getSerializer(Esperandro.java:74)
at com.eallcn.agent.shareprefrence.ConditionSharePreference$$Impl.containerValueAsync(ConditionSharePreference$$Impl.java:24)
at com.eallcn.agent.ui.control.LoginControl.login(LoginControl.java:141)
at LoginControl_Proxy.super$login$void(LoginControl_Proxy.generated)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:525)
at com.google.dexmaker.stock.ProxyBuilder.callSuper(ProxyBuilder.java:523)
at com.eallcn.agent.proxy.callback.AsynMethodAtomInterceptor$1.getResult(AsynMethodAtomInterceptor.java:90)
at com.eallcn.agent.proxy.callback.AsynMethodAtomInterceptor$1.run(AsynMethodAtomInterceptor.java:56)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1080)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:573)
at java.lang.Thread.run(Thread.java:841)
Now ,I use this lib to store a List data like this
ArrayListdistricts = filterConfigEntity.getDistricts();
getSharePrefence(DistrictsListSharePreference.class).containerValueAsync(districts);
but when i get it ,load data will beslowly, many times it's need three seconds or more , can you give me some advice,thanks
Not sure if this is possible, but following (or something functional equivalent) would be nice:
@Default(ofInt = Color.argb(100, 0, 0, 0))
int handleColor();
boolean handleColor(int handleColor);
Any ideas on this?
If I use setter with return type, the cache code is generated after returning the result of the setter...
Generated code looks like following:
@Override
public boolean versionShown(int versionShown) {
return preferences.edit().putInt("versionShown", versionShown).commit();
cache.remove("versionShown");
}
and should look like following:
@Override
public boolean versionShown(int versionShown) {
cache.remove("versionShown");
return preferences.edit().putInt("versionShown", versionShown).commit();
}
Same bug if I use the cache with cacheOnPut = true
...
Now do you have any idea ? thanks
After upgrading to version 2.5.2 or higher, gradle build produces a warning for each preference, e.g.
warning: [unchecked] unchecked cast return (V) (Integer) registrationStatus(); ^ required: V found: Integer where V is a type-variable: V extends Object declared in method <V>getValue(Context,int)
The declaration is as follows:
@SharedPreferences
public interface DefaultPrefs extends SharedPreferenceActions {
...
int registrationStatus();
void registrationStatus(int registrationStatus);
Probably I'm doing something wrong?
How can these warnings be avoided.
regards Rainer
I've an app with a lot of settings and they are growing. I see that I do the same things over and over again like following:
switch (id) {
case 1:
MainApp.getPrefs().booleanSetting1(b);
break;
case 2:
MainApp.getPrefs().booleanSetting2(b);
break;
case 3:
MainApp.getPrefs().booleanSetting3(b);
break;
}
Result wish
// I want to be able to do following
PreferenceManager.Pref pref = PreferenceManager.getPrefByIndex(index);
MainApp.getPrefs().set(pref, b);
Implementation idea - demonstrating boolean values only
// Somehow enable an enum generation
@Cached(cacheOnPut = true, support = true)
@SharedPreferences(name = "mainPrefs", mode = SharedPreferenceMode.PRIVATE, enum = true)
public interface PreferenceManager extends SharedPreferenceActions {
@Default(ofBoolean = false)
boolean booleanSetting1();
boolean booleanSetting1(boolean booleanSetting1);
@Default(ofBoolean = false)
boolean booleanSetting2();
boolean booleanSetting2(boolean booleanSetting2);
@Default(ofBoolean = false)
boolean booleanSetting2();
boolean booleanSetting2(boolean booleanSetting3);
}
// Result
public class PreferenceManager$$Impl implements SharedPreferenceActions, PreferenceManager, CacheActions {
// 1) The implemantion has an enum now like following:
enum Pref {
booleanSetting1(1, "booleanSetting1"),
booleanSetting2(2, "booleanSetting2"),
booleanSetting3(3, "booleanSetting3");
int index;
String name;
Pref(int index, String name) {
this.index = index;
this.name = name;
}
}
// 2) a getter for the pref based on the index
public Pref getPrefByIndex(int index) {
if (index >= 0 && index < Pref.values().length) {
return Pref.values()[index];
}
return null;
}
// 3) setter that works with the pref enum
public boolean set(Pref pref, boolean value) {
cache.put(pref.name, value);
return preferences.edit().putBoolean(pref.name, value).commit();
}
// 4) getter that works with the pref enum
public boolean getBool(Pref pref) {
Boolean __result = (Boolean) cache.get(pref.name);
if (__result == null) {
__result = preferences.getBoolean(pref.name, false);
if (__result != null) {
cache.put(pref.name, __result);
}
}
return __result;
}
}
Any thoughts on this?
java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to com.example.data.Item
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2215)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2265)
When reading an ArrayList<Item>
from SharedPreferences, objects stored in the ArrayList cannot be cast to their initial type (are not de-serialized).
Item class implements Serializable, Esperandro is using its default serializer.
public class Item implements Serializable {
String title;
String desc;
}
@SharedPreferences
public interface LocalStorageInterface {
ArrayList<Item> localItems();
void localItems(ArrayList<Item> items);
}
Methods that return a long or get a long as parameter should use the correct type in the generation.
could you add a default serialiser that does not depend on anything special? No jackson and no gson?
I usually use following in all my projects:
public class SerializationUtil {
public static String serialize(Object object) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(object);
oos.flush();
oos.close();
} catch (IOException e) {
L.e(e);
}
return Base64.encodeToString(baos.toByteArray(), 0);
}
public static <T> T deserialize(String serializedObject, Class<T> clazz) {
if (serializedObject == null) {
return (T) null;
}
byte[] data = Base64.decode(serializedObject, 0);
Object o = null;
try {
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(data));
o = ois.readObject();
ois.close();
} catch (IOException e) {
L.e(e);
} catch (ClassNotFoundException e) {
L.e(e);
}
return (T) o;
}
}
This could be packed into a small extension...
I have a field like following in my preferences to show some info cards to my users and to know which one they have already seen and dismissed:
List<String> hiddenInfos();
boolean hiddenInfos(List<String> hiddenInfos);
void hiddenInfos$Add(String toAdd);
void hiddenInfos$Remove(String toRemove);
After my last relase I suddenly got a lot of crash reports like following:
[SerializationUtil:43 deserialize]: java.io.InvalidClassException: com.my.app.prefs.PreferenceManager$$Impl$HiddenInfos; Incompatible class (SUID): com.my.app.prefs.PreferenceManager$$Impl$HiddenInfos: static final long serialVersionUID =8736114807095266538L; but expected com.my.app.prefs.PreferenceManager$$Impl$HiddenInfos: static final long serialVersionUID =3661852850784779536L;
at java.io.ObjectInputStream.verifyAndInit(ObjectInputStream.java:2343)
at java.io.ObjectInputStream.readNewClassDesc(ObjectInputStream.java:1643)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:657)
at java.io.ObjectInputStream.readNewObject(ObjectInputStream.java:1784)
at java.io.ObjectInputStream.readNonPrimitiveContent(ObjectInputStream.java:761)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:1985)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:1942)
at com.swissarmy.utils.SerializationUtil.deserialize(SerializationUtil.java:40)
at com.my.app.MainApp$1.deserialize(MainApp.java:76)
at com.my.app.prefs.PreferenceManager$$Impl.hiddenInfos(PreferenceManager$$Impl.java:1182)
at com.my.app.managers.UpdateManager.onAppStart(UpdateManager.java:208)
at com.my.app.MainApp.onAfterOnCreate(MainApp.java:151)
...
I would expect that the serialVersionUID
is constant... Maybe it makes sense to define a custom one via annotations? To avoid that the serialVersionUID
is constant as this is essential for deserialisation...
To make access to the preferences thread-safe add a synchronize paramerter to @SharedPreferences.
This should then generate a synchronized block for each method.
Please add a public reset cache function. When using your library with preference fragments, I will have to invalidate any cached values, because preference fragments don't allow to pass in a SharedPreference
object, but only a preference name... Currently I use reflection like following for it:
PrefManager$$Impl prefs = (PrefManager$$Impl) MainApp.getPrefs();
try
{
Field field = PrefManager$$Impl.class.getDeclaredField("cache");
field.setAccessible(true);
Object cache = field.get(prefs);
Method evictAll = cache.getClass().getDeclaredMethod("evictAll", new Class[]{});
evictAll.invoke(cache);
}
catch (NoSuchFieldException e)
{
e.printStackTrace();
}
catch (IllegalAccessException e)
{
e.printStackTrace();
}
catch (NoSuchMethodException e)
{
e.printStackTrace();
}
catch (InvocationTargetException e)
{
e.printStackTrace();
}
Looks like there is lack of proguard config. Here is mine for jackson:
#keep the annotated things annotated
-keepattributes *Annotation*, EnclosingMethod, Signature, InnerClasses
#jackson
-dontwarn com.fasterxml.jackson.databind.**
-keepnames class com.fasterxml.jackson.** { *; }
#esperando
-keepnames class de.devland.** { *; }
-keep class **$$Impl { public *;}
If I set a default value that is the same as the annotations default value I get a compiler warning from here:
Why is this necessary?
// @Default(ofBoolean = false) // <= uncommenting this will produce the warning
boolean test();
boolean test(boolean test);
I always define the default values as I want to see it in my code and to make sure that it is the value I expect it to be and want it to be
With cache enabled, generated code may look like following:
@Override
public DataManager.ExistingDataSources existingDataSources() {
DataManager.ExistingDataSources __result = (DataManager.ExistingDataSources) cache.get("existingDataSources");
if (__result == null) {
Serializer __serializer = Esperandro.getSerializer();
__result = __serializer.deserialize(preferences.getString("existingDataSources", null), com.michaelflisar.gallery.data.DataManager.ExistingDataSources.class);
cache.put("existingDataSources", __result);;
}
return __result;
}
This is a custom serialisable field of mine, the problem is, if it is not initialised or is null, the cache.put("existingDataSources", __result);;
will throw a java.lang.NullPointerException: key == null || value == null
exception.
Can this be changed to allow null values? Any reasons, why this is not possible yet?
Btw, the code generator is generating double ; as you can see in my code example... Not a big deal though, so I'm just mentioning it here...
PS: currently I'm using following version:
compile 'de.devland.esperandro:esperandro-api:2.3.1'
apt 'de.devland.esperandro:esperandro:2.3.1'
Is it possible to use esperandro type safe with PreferenceScreens
written in xml?
A simple solution for me would be that you just generate string resources for each preference key... So that I can reference them in the xml
Any ideas to that?
can i use it with jdk 1.6
It could be nice to store the annotated defaults into the preferences to make use of them in PreferenceActivity
s for example.
http://developer.android.com/reference/android/content/SharedPreferences.Editor.html#apply()
" it's safe to replace any instance of commit() with apply() if you were already ignoring the return value."
Hi there!
A question about this warning:
warning: No SupportedSourceVersion annotation found on de.devland.esperandro.processor.EsperandroAnnotationProcessor, returning RELEASE_6.
How can I fix it please?
Thank you very much!
Inherited methods will not get generated if the superinterface is in a library project instead of the current project.
It would be nice if I could enable a cache, so that I can fastly recheck a setting without reading the settings file again.
A simple HashMap
would be enough and an annotation like @SharedPreferences(..., enableCaching= true)
. And if the user enables caching, a static HashMap is always kept in sync when writing values and reading is done by accessing the HashMap
...
Under specific circumstances ´roundEnv.getElementsAnnotatedWith´ returns way too many elements. If these are fields ´initImplementation´ throws a NPE.
see #36
When putting null or retrieving saved null value the cache will throw NullPointerException
Primitive types that are not natively supported by SharedPreferences
like char and byte fail.
I have a simple class like following (minimal extract of my file) using v2.4.1:
@Cached(cacheOnPut = true, support = true)
@SharedPreferences(name = PrefManager.PREF_NAME, mode = SharedPreferenceMode.PRIVATE)
public interface PrefManager extends SharedPreferenceActions, CacheActions
{
String PREF_NAME = "mainPrefs";
// a lot of default preferences, ALL have a default value
// Example:
@Default(ofInt = 0)
int versionShown();
void versionShown(int versionShown);
// some custom serialised values without default value
DataManager.ExistingDataSources existingDataSources();
void existingDataSources(DataManager.ExistingDataSources existingDataSources);
}
What I do
PreferenceFragments
Problem
Sometimes after I return from my SettingsActivity
the preferences are completely resetted. I'm sure I'm using esperandro anywhere in my code and that only the PreferenceFragment
s directly manipulate the preference file. Any ideas how this can happen? Do you see a problem in my code?
CODE
My initialisation function
public static void initPrefs()
{
if (MainApp.getPrefs().existingDataSources() == null)
MainApp.getPrefs().existingDataSources(new DataManager.ExistingDataSources());
int versionShown = MainApp.getPrefs().versionShown();
if (versionShown == 0)
{
MainApp.getPrefs().initDefaults();
// force saving all prefs to the file, we need this so that the PreferenceFragments work correctly
MainApp.getPrefs().get().edit().commit();
}
}
My settings activity
public class SettingsActivity extends AppCompatActivity
{
@Override
public void onDestroy()
{
// reset all cached values in esperandro, we may have changed them without using esperandro!!!
MainApp.getPrefs().resetCache();
super.onDestroy();
}
}
My base PreferenceFragment
, a minimal version
public abstract class BasePrefFragment extends PreferenceFragmentCompat
{
@Override
public void onCreatePreferences(Bundle bundle, String s) {
// ATTENTION: we use the same pref file as esperandro!
getPreferenceManager().setSharedPreferencesName(PrefManager.PREF_NAME);
addPreferencesFromResource(getPreferenceId());
}
public abstract int getPreferenceId();
}
Should be easier since Android is gradle based a long time now...
After migrating to androidx I have following problem:
The generated preference class is importing androidx.core.util.LruCache
instead of androidx.collection.LruCache
. Problem happens with following setup: @Cached(cacheOnPut = true, support = true)
, only ifsupport = true
is used of course.
Okay, so I realize this may be a bit of a strange issue. I'm not exactly sure how to help debugging, so please feel free to point me in the right direction.
Both you and dagger depend on the Square JavaWriter. When Dagger 1.2.2 is used in my dependencies, I get the following error:
Caused by: java.lang.NoSuchMethodError: com.squareup.javawriter.JavaWriter.beginControlFlow(Ljava/lang/String;)Lcom/squareup/javawriter/JavaWriter;
at de.devland.esperandro.processor.PreferenceEditorCommitStyle.emitPreferenceCommitActionWithVersionCheck(PreferenceEditorCommitStyle.java:25)
at de.devland.esperandro.processor.Putter.createPutter(Putter.java:152)
at de.devland.esperandro.processor.Putter.createPutterFromModel(Putter.java:89)
at de.devland.esperandro.processor.EsperandroAnnotationProcessor.processInterfaceMethods(EsperandroAnnotationProcessor.java:124)
at de.devland.esperandro.processor.EsperandroAnnotationProcessor.process(EsperandroAnnotationProcessor.java:78)
at com.sun.tools.javac.processing.JavacProcessingEnvironment.callProcessor(JavacProcessingEnvironment.java:793)
at com.sun.tools.javac.processing.JavacProcessingEnvironment.discoverAndRunProcs(JavacProcessingEnvironment.java:722)
at com.sun.tools.javac.processing.JavacProcessingEnvironment.access$1700(JavacProcessingEnvironment.java:97)
Using Dagger 1.2.1 doesn't produce this error, everything compiles just fine.
I've tested this on both a Linux and Windows host, so I don't think the issue has anything to do with Java itself. My project is over here for a reproducible case.
I can file an issue with Dagger as well if you want me to blame them, I'm just really not sure what could be causing this, but figured you should know.
Thanks, let me know if there's anything I can do to help!
Preference Interface should allow definition of putters with boolean return value. Generated class from Esperandro should return the value of the call to commit().
Currently one can define default values at compile time using @Default
annotation. Unfortunately this works only for simple types and at compile time. If user wants to specify default value for complex object, that can't be passed to annotation, he can't do that.
My propose is to introduce getters with additional parameter that should be used as default. The library can have a convention that such methods should end with *Default
@SharedPreferences
interface MySettings{
List getMyList(); //common usage
List getMyListDefault(List defaultValue); //using default value if neccessary
}
The generated code would be not much more complex. Seems that the library would have to check for existence of the value for complex objects. For simple objects it can just pass parameter to shared prefs.
Add the possibility to register a global shared listener that will be registered at each distinct sharedPreference.
Will it be possible to support incremental APT?
https://docs.gradle.org/4.7/userguide/java_plugin.html#sec:incremental_annotation_processing
Add a check to initDefaults that no user set value will get overridden.
This way initDefaults can be called at initiialization without caring about previous set values.
(useful for app updates when preferences ared added)
Include recent examples and be a bit shorter in explanations
Currently, if I use serializable lists, they are not working, as the generated code is wrapping a list in a container, that is NOT implementing serializable itself...
NOT Working: Using following does crash, as the wrapper class is not serializable:
ArrayList<String> existingSources();
boolean existingSources(ArrayList<String>existingSources);
working
OwnWrapper existingSources();
boolean existingSources(OwnWrapper existingSources);
With OwnWrapper
like following:
public static class OwnWrapper implements Serializable
{
public ArrayList<String> sources;
public OwnWrapper ()
{
sources = new ArrayList<>();
}
}
I need a complete gradle sample object for an easier start for newbies.
In v2.2.0, initDefaults
is generated, in v2.6.0 or v2.7.0 it is empty...
A package protected interface will have an implementation that is public.
Use the same class modifier as the implemented interface.
Whenever changing a value, this may be blocking the UI if it happens on the UI thread... I think, most of the time, async setters would be fine as well (and maybe better?).
So something that allows me to decide if commit()
or apply()
should be used would be really nice... Maybe a setter function with a boolean parameter additional to the default? This would not break any existing API.
Instead of generating following:
@Override
public boolean lastSelectedColor(int lastSelectedColor) {
cache.put("lastSelectedColor", lastSelectedColor);
return preferences.edit().putInt("lastSelectedColor", lastSelectedColor).commit();
}
You could generate something like following:
@Override
public Boolean lastSelectedColor(int lastSelectedColor) {
return lastSelectedColor(lastSelectedColor, true);
}
@Override
public Boolean lastSelectedColor(int lastSelectedColor, boolean commitImmediately) {
cache.put("lastSelectedColor", lastSelectedColor);
Editor editor = preferences.edit().putInt("lastSelectedColor", lastSelectedColor);
if (commitImmediately)
return editor.commit();
editor.apply();
return null; // we don't know the success state...
}
In my gradle console log I always see following:
warning: No SupportedSourceVersion annotation found on de.devland.esperandro.processor.EsperandroAnnotationProcessor, returning RELEASE_6.
warning: Supported source version 'RELEASE_6' from annotation processor 'de.devland.esperandro.processor.EsperandroAnnotationProcessor' less than -source '1.8'
I'm using java 8 (for lambdas and other new java features) in all my projects, would it be possible to get rid of this warning? Could you add java 8 support to this library?
It's no big deal as it's a warning only and it does not have any effect, still...
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.