Closed MFlisar closed 7 years ago
Interesting idea. But I have to look more into detail for this. I think there is a problem with the build order. Additionally I don't like the compilation being dependent of something generated. (the interfaces for esperandro are always there at compile time, so you will never import something that is not there yet; but if I generate something at build time that is needed at build time I could run into errors)
But I like the general idea of having more stuff tied to esperandro.
Actually depending on compilation results is common, even view binging works that way (only after compiling you can access the class)
I just thought about generating the resources because this probably is the easiest solution and can be done fastest...
I'm currently writing my setting screens for an app and have about 100 preferences I think... Having so many preferences, I would say the string resource solution would already fulfill all my needs... Auto complete + typo safe + if I rename something the compilation will fail the next time...
I just wrote all my variables to the console and then copied them into a preference.xml
file... Code looks like following (maybe interesting for someone else in the meantime until you thought about if and how you implement that).
This is just a fast solution though...
public class Temp
{
private static final String PREF_RW = "\t<string name=\"%s\" translatable=\"false\">%s</string>\n";
// preferenceClass should be the preference interface
public static void printPreferenceNamesAsStringResources(Class<?> preferenceClass)
{
Method[] methods = preferenceClass.getDeclaredMethods();
StringBuilder sb = new StringBuilder();
sb.append("<resources>");
for (int i = 0; i < methods.length; i++)
{
Method m = methods[i];
if (m.getParameterTypes().length == 0)
addPref(sb, m);
}
sb.append("</resources>");
log("PreferenceStrings", sb);
}
private static void addPref(StringBuilder sb, Method m)
{
sb.append(String.format(PREF_RW, m.getName(), m.getName()));
}
private static void log(String tag, StringBuilder sb)
{
if (sb.length() > 4000)
{
int parts = sb.length() / 4000;
for (int i = 0; i <= parts; i++)
{
int max = 4000 * (i + 1);
if (max >= sb.length())
Log.v(tag, sb.substring(4000 * i));
else
Log.v(tag, sb.substring(4000 * i, max));
}
}
else
Log.v(tag, sb.toString());
}
}
I think I could generate String resources like you said if I write a gradle plugin. I have to look into it a bit more. With the current solution I could possibly generate them into the wrong output folder ;) Will think about this a bit more. A bit more input is welcome. @danielneu
I have to admit that I had no hands on Android since what feels like years, with no IDE installed anymore. I like the idea as well, but I am most likely not able to contribute anything else.
Sorry :-/
@dkunzler anything special you need to know because you are asking for more input?
Actually, the solution should be quite simple, but I've no experience with annotation processors. I checked that a little bit already but I don't even know how to create a file in a special destination.
The destination for the output should be a seperate xml file in res\values
, a file like preferences.xml
for a class named Preferences.java
for example, maybe even with a esperandro_
, esp_
or similar prefix? To avoid conflicts...
@MFlisar I hacked something together that should work well. If you don't mind you could beta test the feature and give feedback.
To activate for you:
add my maven repo to top buid.gradle (or wherever you like it):
allprojects {
repositories {
jcenter()
maven {
url "http://maven.devland.de"
}
}
}
add snapshot dependencies:
compile 'de.devland.esperandro:esperandro-api:2.3.2-SNAPSHOT'
apt 'de.devland.esperandro:esperandro:2.3.2-SNAPSHOT'
Add this to your apps build.gradle to tell esperandro where your resources are:
apt {
arguments {
esperandro_resDir android.sourceSets.main.res.srcDirs[0]
}
}
Add the @GenerateStringResources
Annotation to your preference interface. You can customize the stringPrefix
which means a prefix that is added to the defined string instead of only using the name. Additionally you can set filePrefix
to add a prefix to the generated xml file.
The files are generated directly into your project. It was the only way I found without writing a custom gradle plugin. (which would mean to migrate this whole project to gradle I think)
Every feedback is welcome.
I'll test that... You did not change anything else, did you? Only code that generates the xml string resource file was added...
I've two apps I will test this with today...
My observations so far:
esperandro
as default prefix instead of the upper case variant...translatable="false"
to each string resource, otherwise you have to change the default lint checks for this attribute (which actually is very useful)Additional observations:
When I try to make a release build I get following exception:
Error:Error converting bytecode to dex:
Cause: Dex cannot parse version 52 byte code.
This is caused by library dependencies that have been compiled using Java 8 or above.
If you are using the 'java' gradle plugin in a library submodule add
targetCompatibility = '1.7'
sourceCompatibility = '1.7'
to that submodule's build.gradle file.
Adding the `compileOptions` to my project does not help...
I uploaded a new SNAPSHOT to my maven repo.
Changes:
BTW: I'm also thinking about generating another class that can be accessed directly which contains the names of the preferences as string constants to be used in code interacting with the preferences directly. What do you think of that?
Looks good now. Thanks
I don't mind if you add this, but this only increases speed a little bit in some cases. context.getString(...)
can be used in code as well with the generated string resources...
Can you tell me how to convert this statement for the new annotation processor?
apt {
arguments {
esperandro_resDir android.sourceSets.main.res.srcDirs[0]
}
}
Tried following:
javaCompileOptions {
annotationProcessorOptions {
arguments = [
squidbOptions : 'androidModels',
esperandro_resDir : android.sourceSets.main.res.srcDirs[0]
]
}
}
Tried esperandro_resDir : 'src/main/res'
, but this does not work either...
Edit
esperandro_resDir : 'C:/my/absolute/path/src/main/res'
works, do you know how to get the absolute path?
I will look into this. What versions are you using currently? AS, Gradle, android-gradle-plugin, apt-plugin. And a redacted version of your build.gradle would also be nice. Thanks
I use version 2.4.1, gradle 4.0, latest stable android studio, NO APT PLUGIN but the newer annotationProcessor
Can only add the gradle file later...
That's what I use in the build.gradle
:
compile 'de.devland.esperandro:esperandro-api:2.4.1'
annotationProcessor 'de.devland.esperandro:esperandro:2.4.1'
Thanks, I will definately setup a test project and look into it later. Didn't have the time yesterday, sorry.
No need to hurry, a hard coded path does work anyways... Still, not useable for an open source project or a shared project...
Two solutions are possible:
buid.gradle
, not sure how to retrieve it though. Something like root + '/src/main/res'
...Hi,
the following inside android { defaultConfig {...} } works for me locally.
javaCompileOptions {
annotationProcessorOptions {
arguments = [
esperandro_resDir : android.sourceSets.main.res.srcDirs[0].getAbsolutePath()
]
}
}
Reach back if it doesn't work. Unfortunately I don't see any possibility to get the desired res dir right from the processor.
Auto complete does not show me getAbsolutePath()
that's why I did not try this...
Works now, thanks.
But it generates the strnigs in the main/res/values
folder instead of the main/res/values/strings
Yeah, because that is the path where strings are going. If you put them in values/strings you could add + "/strings"
to the path.
I'm still thinking about a better solution but the annotation processor had not enough information to find the res folder on its own. Perhaps the new integrated one gives more information.
Would love to make this feature publicly available wihtout the need of configuring something in the build.gradle.
I know. You should put this into your readme. As far as I know it's the one thing that's unique to your library, others don't have that. Just like the caching feature.
Supporting generation of strings without setup would be nice. Maybe you can traverse the folder hierarchy until you find the root with the gradle file and then search for the res folder... such an idea. Path of the annotated class is known as far as I remember from my annotation library.
Issue is solved, thanks for your help.
@MFlisar FYI I just released 2.5.0 (should be available on central in a few hours) Features that are potentially interesting for you:
esperandro_valuesDir
add
and remove
operations on serialized Collections with the following syntax. If you find that useful you could try it.
List<String> listPreference();
void listPreference(List<String> listPreference);
void listPreference$Add(String toAdd);
void listPreference$Remove(String toRemove);
I will update the documentation for all that later that week.
Tried the list functions already, very useful.
Ever thought about the possibility to create a preference screen from the preference setup?
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?