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
Related
Whenever we want to inflate a view or get a resource we have to cast it in run-time. views, for example, are used like so:
In the past, we would have needed to cast it locally
(RelativeLayout) findViewById(R.id.my_relative_layout_view)
Now, we use generics
findViewById<RelativeLayout>(R.id.my_relative_layout_view)
my question is why doesn't the compiler(or whoever generates the R class) doesn't also keep some kind of a reference to the type of the element(doesn't matter if it's a string or an int or any other type) that way casting problems should not occur
We cannot really speculate on that, that would be a design choice.
It might be that they wanted to avoid bloating the APK. Every ID would need a full package name to the class. So would each ID in android.R too. Since R is packaged in every APK.
Solutions
However, if you are using Kotlin, you can even do away with the generics check. Kotlin will determine it automatically.
val view = findViewById(R.id.my_relative_layout_view)
view.method()
Or event simpler, if you use synthetics:
my_relative_layout_view.method()
Also, if you are using data bindings, you can just access it like this:
binding.my_relative_layout_view.method()
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/
Because of just one wrong written line of code in FloatingToolbar.java i was force to copy the entire source code in my library to patch it. This work but now the problem is that this unit call also com.android.internal.R. The problem with com.android.internal.R is that it's could be different between release, (because it's internal), so to be safe i must also duplicate the definition
in r class i have for example :
public static final class layout {
public static final int floating_popup_open_overflow_button=xxx
}
where to find the xml (i think it's an xml?) that define floating_popup_open_overflow_button ? actually in \android-sdk-windows\sources\ i can find only the java files
where to find the xml (i think it's an xml?) that define floating_popup_open_overflow_button ?
You can find a copy in $ANDROID_SDK/platforms/android-NNN/data/res/layout/, where $ANDROID_SDK is wherever you have installed the Android SDK and NNN corresponds with the version of the Java that you forked. There may be other variants of floating_popup_open_overflow_button.xml in peer directories (e.g., layout-xlarge); I have not checked them all.
I am trying to use the library DBFlow in Android. I have used it before and in the older version (2.2.1) it used a $Table.field. Now it seems to have another format where we reference a new class by "_Table".
Example:
int taxBracketCount = SQLite.select(count(Employee_Table.name))
.from(Employee.class)
.where(Employee_Table.salary.lessThan(150000))
.and(Employee_Table.salary.greaterThan(80000))
.count();
Where and when are these "_Table" classes created? How do I access them?
(Even if I wanted to use and older version, my newly created studio project do not create the $ files either. Some explaination of this, or both, would be nice)
You need to run a successful build for the files to be generated. Make sure your code can compile, so remove any references to the "_Table" classes and run your project first and then you should be able to access them.
I got weird errors recently as below, said it couldn't find those "$Table" classes, but actually they had been built and in there.
I commented and uncommented every new java files. And eventually I found that's because there is no "#PrimaryKey" in one model class of DBFlow.
So, you have to define the bloody "#PrimaryKey" for your DBFlow model classes( and don't forget to extends BaseModel as well).
PS: DBFlow Version 3.0.0-beta
/Users/XXX/code_projects/###/src/main/java/com/XXXXX.java:9: error: cannot find symbol
import com.XXX.databasemodel.XXX$Table
_Table classes and their corresponding methods to communicate with the database are created when you build your project. You can build it even with these errors, and they'll be created on that moment.
If you're still facing the issue, make sure you're adding the #Table annotation on top of your class, else they won't be created.
I have faced the same problem, but the reason was an absence of the annotation
#Table(databaseName = AppDatabase.NAME)
at the top of
public class AwesomeModel extends BaseModel
#PrimaryKey(autoincrement = false)
#Column #Expose long id;
public long getId() {
return id;
}
}
class...
Ok so I was also facing similar issue -
And like someone else has also pointed out, the app needs to build to solve this. But removing all _table references first and removing them is a long task. I followed following steps -
try to rebuild the app, it will fail
Now head over to Problems tab in Android Studio
Try to identify any issues other than _table - fix that issue and build again - voila!
If I know a variable's pattern such as R.id.edit_x where x (1..N), how can I get a reference to a given EditText, like findViewByID(R.id.edit_1). Is there something like an "eval" function in Dalvik ? Thanks.
Try Java reflection. Discussion on retrieving static final fields via reflection is here - Accessing Java static final ivar value through reflection
hoha's answer is good. Another thing you can do is create a look-up table that maps 1..N to the resource IDs. (Presumably you know all the resource IDs ahead of time.)
maybe, you can check roboguice. it is a ioc framework for android and it's realy easy to use. i copy some code from the sample from the project to show how to use it:
public class AstroboyMasterConsole extends RoboActivity {
#InjectView(R.id.self_destruct) Button selfDestructButton;
#InjectView(R.id.say_text) EditText sayText;
#InjectView(R.id.brush_teeth) Button brushTeethButton;
#InjectView(tag="fightevil") Button fightEvilButton; // we can also use tags if we want
}
then you can you these injected variables in your code!