final field in R2.java in ButterKnife - android

this is an extension of Android: Why do we need to use R2 instead of R with butterknife? and Reference in R.java not final
I understand that fields of R.java in the library project do not have final modifier to protect value collisions between library projects. However ButterKnife recovers final modifier in R2 and uses it.
I think this goes to the collision problem and values can collide, but there is no problem. How does it work?
===
I add examples.
There are one main project and one library project. The main project has com.main.R and the library project has com.library.R.
When there is a collision:
com.main.R: public static final int example = 0x1234
com.library.R: public static int example = 0x1234
if build tools does not recompile the library project, how can we avoid the collision between those values?
When ButterKnife creates R2
com.main.R: public static final int example = 0x1234
com.library.R: public static int example = 0x1234
com.library.R2: public static final int example = 0x1234
com.library.R2 has a collision and even it has a final modifier. Doesn't it produce a problem? why?
Thanks

Although final keyword has been removed from the R.java generated class since it caused a negative impact on the build performance, Butterknife uses only final for itself which is why only the Butterknife code needs to be recompiled everytime a new id is to be added. The good point is Butterknife uses annotations in order to make sure that type that we return is always correct.
From the doc:
In other words, the constants are not final in a library project. The reason for this is simple: When multiple library projects are combined, the actual values of the fields (which must be unique) could collide. Before ADT 14, all fields were final, so as a result, all libraries had to have all their resources and associated Java code recompiled along with the main project whenever they were used.
This was bad for performance, since it made builds very slow. It also prevented distributing library projects that didn't include the source code, limiting the usage scope of library projects.
The reason the fields are no longer final is that it means that the library jars can be compiled once and reused directly in other projects. As well as allowing distributing binary version of library projects (coming in r15), this makes for much faster builds.
Imagine If R.java was still supposed to contain the final keyword, for every library module that you've included in your android project, recompilation needed to be done since then R.java would be used by both the Android source code and libraries. Hence final keyword has been removed now. However, Butterknife still uses the final as it doesn't care about the clashes but BindView annotations internally use those type-annotations like #IdRes, #StringRes, #DrawableRes, etc inside that ensures the type safety of variables declared to be consistent.

I investigated further. In summary,
Resource values in library projects are reassigned when main project is built
"Final" needs to be removed to use reassigned values
Values in R2 is not reassigned so it might conflict, but it is only marker for Annotation. The actual value used when finding views comes from R
Details are here:
https://battleshippark.wordpress.com/2018/02/12/butterknife-library-project-r2-final/

Related

Resource IDs will be non final in AGP 7.0

I have an annotation processing library that generates RecyclerView adapters in compile time.
I'm currently rebuilding it from the ground up with many changes and improvements, but while testing, I received a warning stating:
Resource IDs will be non-final in Android Gradle Plugin version 7.0,
avoid using them as annotation attributes
This is a problem because it means I won't be able to use R.layout variables in annotations.
I currently use it to associate the layout file's integer value with the R class variable name; this is to locate the layout file from the resource folder and later call inflate(layoutResId).
Currently, I solve this issue like so
Example
Given a simple ViewHolder annotation.
annotation class ViewHolder(val layoutResId: Int)
With the usage
#ViewHolder(R.layout.sample)
data class Sample(val text: String) : GencyclerModel
And the Generated R.layout class.
public final class R {
public static final class layout {
public static final int sample = 567541283;
}
}
When processing the ViewHolder annotation, the processor would receive the integer value 567541283.
In the first processing cycle, the processor would analyze the R class and create a table to map the integer to the layout name, in this case, 567541283 <-> sample.
With that info, I can iterate over the layout resource directory and find the layout file with the name sample.xml.
and I can also later call inflate(R.layout.sample)
The field will be non-final in the new version, thus throw a compile-time error.
An annotation argument must be a compile-time constant.
Possible solutions
(Butterknife solution) Creating a duplicate R class that will generate the R.layout variables as static final, thus removing my R class dependency.
(AndroidAnnotations solution). Using strings instead of the Resources class. I'm not too fond of this solution because it will cause issues if the layout is renamed or a typo.
I'm not sure how happy I feel about both, but I honestly don't see other ways to solve it.
If anyone has a better way to solve this, I would love to hear, and if not, which solution would you prefer?
Thanks
I'm linking the issue that I opened in the GitHub project in case you want to contribute.
Another solution would be adding a method to GencyclerModel which returns the layout reference and remove that reference from your annotation. I guess the only problem this approach is going to make, is your checking condition about the existence of layout file in your compiler. But using this approach you won't hardcode layout references in annotations and you'll get them from a method inside each model

Android: Why do we need to use R2 instead of R with butterknife?

I've been using butterknife for a few months and I've just noticed in its documentation that it says:
Now make sure you use R2 instead of R inside all Butter Knife
annotations.
Why is that? I've been using R and everything works perfect.
Using R2 is only necessary for building Android Library projects.
https://github.com/JakeWharton/butterknife#library-projects
It has to do with fact that the values in the R.java generated class aren't declared as "final" when built as part of a library. The annotations used for #BindView() need these R. values to be final and not subject to change later on.
I'm sure someone could explain all of this better -- but the bottom line is - you're just fine using "R." values in #BindView in an Android application

IntDef annotation in Android

In Android, there is a recommendation that developers should not use enum. Instead of enum, Android supports some annotations, like #IntDef.
I tried this annotation is a simple project. My code looks like this:
#IntDef({APPLE, BANANA})
public #interface Fruit {
}
public static final int APPLE = 1;
public static final int BANANA = 2;
private #Fruit int mFruit;
If I try to set variable mFruit to 6 for example, I see the message that it must be one of APPLE or BANANA. This is fine, it works great.
When I paste this code to my second application and I try to set variable to different value than APPLE or BANANA, it works and there is no warning message and I am able to build application.
So, my question is: is there any way how can I disable or enable support of this annotation? What can cause this different behavior, that some project supports it and some project don't? I am using the same version of Android Studio for both projects.
You probably need to explicitly set retention policy. Look at the official Android IntDef example, where retention policy is set to #Retention(SOURCE)

Android resources key collision

I have two Android projects, a main one (package name com.adip.sampler) and a library that is added to main (package name com.samples.projb). In both of them in resources I have an integer-array with same key: my_int_values:
In main project:
<integer-array name="my_int_values">
<item>10</item>
<item>20</item>
<item>30</item>
<item>40</item>
<item>50</item>
<item>60</item>
<item>70</item>
<item>80</item>
</integer-array>
while in library:
<integer-array name="my_int_values">
<item>34</item>
<item>35</item>
<item>36</item>
<item>37</item>
</integer-array>
In main project from an activity if I am investigating what are the values from these arrays (both main project and library):
protected void showLocalStrings() {
Log.d("RESSampler", "In Main: " + Arrays.toString(getResources().getIntArray(com.adip.sampler.R.array.my_int_values)));
Log.d("RESSampler", "In Libr: " + Arrays.toString(getResources().getIntArray(com.samples.projb.R.array.my_int_values)));
}
then I'm seeing this in Logcat:
In Main: [10, 20, 30, 40, 50, 60, 70, 80]
In Libr: [10, 20, 30, 40, 50, 60, 70, 80]
It seems that main project is overriding the values defined in library array ... I doubled checked if I am reading from resources with correct key and that is ok. Until I took a look in each generated R class. In the main project this is what I have for com.adip.sampler.R.array.my_int_values:
public static final class array {
public static final int my_int_values=0x7f060000;
}
while in library project com.samples.projb.R.array.my_int_values:
public static final class array {
public static final int my_int_values = 0x7f060000;
}
Android tool has generated the same value, so no wonder I am getting this behavior. I can get rid of this behavior if I change the key from one of the integer arrays, but imagine that you have some big projects with a lot of resources, dependency libraries and sooner or later you may bump into this kind of issue: having the same type of resources with the same key value (I've checked with string and with string-array and above behavior appears there as well). So the questions would be:
Why this issue appears? Or if it's not an issue what explains this behavior?
How to avoid it best? I am guessing that trying to have some kind of uniqueness in defining the keys will do the trick, but developers tend to be lazy ...
This appears using multiple variants of latest ADTs and Eclipse versions (Juno and Indigo). Checked on Windows only.
Reading from Library Projects at Android Developers, there are many references where they clearly say merging happens at build time and resources with same IDs overwrite each other.
For resources with same ID from a library and application
In cases where a resource ID is defined in both the application and
the library, the tools ensure that the resource declared in the
application gets priority and that the resource in the library project
is not compiled into the application .apk. This gives your application
the flexibility to either use or redefine any resource behaviors or
values that are defined in any library.
For resources with same ID from two libraries
... your application can add references to multiple library projects, then
specify the relative priority of the resources in each library. This
lets you build up the resources actually used in your application in a
cumulative manner. When two libraries referenced from an application
define the same resource ID, the tools select the resource from the
library with higher priority and discard the other.
Solution suggested in documentation
Use prefixes to avoid resource conflicts
To avoid resource conflicts for common resource IDs, consider using a
prefix or other consistent naming scheme that is unique to the project
(or is unique across all projects).
How to set priority in libraries from command line
If you are adding references to multiple libraries, note that you can
set their relative priority (and merge order) by manually editing the
project.properties file and adjusting the each reference's .n index as
appropriate.
Personally, I would change the names of the resources. If you read anything about naming conventions, it should be something meaningful and "my_int_array" isn't really too helpful, especially in a library that other people or project could potentially use.
Ideally, you want to be able to forget about this for 6 months, come back and look at it and know what that array is for/contains, without having to delve through code to deduce what the array is for by what's done with it.
This post; https://stackoverflow.com/a/7249410/1222199 contains a few different answers on naming conventions.
Finally, not sure about the conflict, couldn't dig anything up. I'm guessing it's to do with the way it's automatically generated, might be worth logging it as a bug and see if you get any response from the development team.

Android: Is it possible to alias R from app and lib to distinguish between the two?

Can I somehow alias a generated R file from a library and a generated R file from an application?
IE: I have two projects, one being a library. I reference the library in the application.
The library creates one R file, the application creates two: com.example.mapplication.R and the R from the referenced library, com.example.mlibrary.R. However, since I want to use both frequently in the application, I want to alias both the R classes. For example, call the library R something like libR and the app R something like appR.
I tried a couple of things which are of such stupidity, I won't even fully mention them, but I did some things like private com.example.mapplication.R appR = com.example.mapplication.R (immediately realising this wasn't gonna work), and private Class<com.example.mapplication.R> appRclass = com.example.mapplication.R.class;, but that didn't give me the desired effect. I do, however, use the latter method and some reflection to make it possible to be able to get the field and inner classes like this:
Class<com.example.mapplication.R> appRclass = com.example.mapplication.R.class;
int activity_main_layoutid = Toolkit.getFromClass(appR).
getMeTheInnerClass("layout").getMeAField("activity_main");
I haven't fully implemented the Toolkit method, but as we all know refletion can do this kind of stuff, but at the end of the day it's just as much work as just typing com.example.mapplication.R or com.example.mlibrary.R everywhere.
So to wrap thing up: Is it possible to alias multiple R's so that I can use appR and libR to distinguish the two?
I wish we could, but according to what I know, it isn't possible, at least not yet.
I also wish we could put resources in sub folders, but even this is impossible.
What you can do is setting a unique prefix (rename each resource file) for each library, as it's done by some third party libraries (like actionBarSherlock, which uses "abs" as the prefix).
Alternatively, you can have a special trick and put the files in the src folder and reach them from there, but that's more of a workaround, since the R won't have a reference to any of the resources there.
For example, you can put an image called image.png into the src/com/company/app_name, and then create a bitmap from it using:
final InputStream fis = getClass().getResourceAsStream("/com/company/app_name/image.png");
final Bitmap bitmap = BitmapFactory.decodeStream(fis);
By the way, if you've thought of using the assets folder, you can't do it for library projects, since it requires you to have the files in the project that uses the library project. It will work, but it misses the encapsulation idea...

Categories

Resources