-
Notifications
You must be signed in to change notification settings - Fork 4.6k
Support Android Library Projects #613
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Wish the processor could have extracted the fully qualified names by itself so the plugin wasn't necessary for that, but can't have it all :). Awesome work |
7536f36
to
c23a2b0
Compare
This solution is similar as Butterfork. Users have to replace R by R2/B. I found a better solution ButterCookie. Using ButterCookie, you don't need to change any existing code with Butterknife in Library project. |
I have looked at ButterCookie, but here are some issues I noticed with it
It would need to keep up with every possible combination of changes in butterknife/the android gradle plugin/apt plugin and would become very hard to maintain. |
This is slightly different than Butterfork because
|
@kageiit Fully agreed on the points about Butterfork, I will deprecate it as soon as this gets merged. Great work! 👍 |
Could |
Also, instead of annotating a random type with e.g. @RClass(R.class)
package my.android.library; would setup |
@GrahamRogers I spent a lot of time trying to generate R2 using during the annotation processor rounds. The problem lies in the fact that since R2 does not exist before the first round, the processor would need to defer processing on all the butterknife annotated elements to the next round. But then, the type mirror of R2.id.value etc would have changed on the next round, so the int value cannot be extracted easily by querying the value parameters on the butterknife annotations. It also makes the logic inside the processor needlessly complex as it would have to handle the cases when the RClass annotation exists and not. Would be hard to maintain as butterknife evolves as well. Using apt for R2 generation means the generated R2 class will end up in the aar as well which is not desirable. I settled on this approach as a nice balance between ease of use, a clean api and maintainability. The location of where to put the RClass does not matter, but the import to R does not exist yet if it's on top of a package element. One would then have to use the fully qualified name, but I prefer for it to look terse. Also, the package of R2 is always the same as R, so putting it on top of a package element does not actually use any information from that element. The decision of where to put the RClass annotation can be left to the consumer and we can suggest an appropriate location as best practice. |
For the Bazel case, since there is one Android library per package annotating the package would make sense since the annotated package would be the same as the package used for Also, you can do this: @RClass(R.class)
package my.package;
import butterknife.RClass;
import my.other.package.R; Although I admit it would be weird for that to produce You raise good points about using code generation for |
Not a big fan about adding a package target for RClass. The package for R2 is decided by the plugin of the build system so you could just put that detail in bazel to allow it. The same goes for buck. The build systems are responsible for generating R. They should be able to generate R2 as well. I don't think a plugin will be provided out of the box for buck/bazel in butterknife. I'm planning to add first party support for R2 in buck after this merges. You may need to propose the same in bazel for your use case. |
|
||
apply plugin: 'com.android.library' | ||
apply plugin: 'com.neenbedankt.android-apt' | ||
apply plugin: 'butterknife' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's unfortunate that you need both a plugin and the @RClass
annotation. Couldn't this plugin modify the annotation processor options or register a task to generate a file with the right information?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RClass and plugin are two separate things. RClass is used as a hint to the processor to find the symbol mapping to generate nicer generated code. It works on apps without using the plugin.
The plugin is just used to create a R with all final values.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it. You might be interested in this issue I filed a while back for exposing things to processors automatically.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Libraries depend on the plugin so no issue there. For apps that don't depend on it; "if you want nicer generated code, add the @RClass
annotation" and "if you want nicer generated code, add the plugin" sounds like equivalent effort for the consumer.
Moving the responsibility to the plugin does mean it needs to be replicated for each build system (Buck, Bazel etc.). The variant.javaCompile.options
method might not be as straightforward, but "generate a file with information" should be simple enough to do for each?
Native support by android-tools would definitely be awesome. Parsing the manifest and deriving information always feels a bit hacky.
I think the effort of having to use RClass in conjunction with the plugin is minimal. In buck, we just need to add an option for generating R2 and the rest will work the same. Modifying the annotation options as part of buck itself just to support usage of butterknife is not ideal. The same goes for bazel. This way, there is no need to wait on android-tools for this support, since that will be purely be scoped to only gradle users. Once that support is available, we can make the usage of R2 optional as well for gradle users by enhancing the plugin. |
We should also not rely on variant.javacompile dsl as that is not compatible with Jack. The android apt plugin currently does not work when Jack is enabled due to the same reason. |
Also, if we go down the approach of "generating a file with information", anytime that file format changes, all build systems would need to fix their logic based on the version of butterknife they depend on which is not desirable. This decouples the logic and is single responsibility for the annotation and the plugin. |
62e51c2
to
544f578
Compare
private Elements elementUtils; | ||
private Types typeUtils; | ||
private Filer filer; | ||
private Trees trees; | ||
|
||
private final Map<Integer, Id> symbols = new HashMap<>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: LinkedHashMap
+ " }\n" | ||
+ " @SuppressWarnings(\"ResourceType\")\n" | ||
+ " protected static void bindToTarget(Test target, Resources res) {\n" | ||
+ " target.one = res.getInteger(test.R.integer.res);\n" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like we can clean this up so that it's not always going to use a FQCN. I can do that in a follow-up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ya for sure. i didnt get time to clean that part up.
First IDE import is problematic:
The one thing we do in other projects is keep the samples on the release version and only update them after a new release. I'll probably do that here too once the version with this is released. |
merged as 20aad6c |
Buck support here: facebook/buck#789 |
Summary
There have been many solutions proposed to support using Butterknife in Android Libraries i.e non-final IDs in R. This approach uses a combination of Gradle Plugin + Annotation Processing + Custom Lint to ensure this is done as safely as possible with minimal configuration.
Usage
The plugin generates an
R2
class which is essentially a copy ofR
with final values and the correct support annotations.R
class for the library using the@RClass
annotation, just once, anywhere in code. UseR2
instead of R in all butterknife annotations.The
@Rclass
annotation is used by the annotation processor to map the integer ids to symbols inR
. This in turn generates code likein the
ViewBinder
. This ensures that the final values used fromR
values will be generated by the app consuming the library.R2
is being used outside butterknife annotations, a custom lint rule will throw an errorThis is important because values in
R2
are final and would be inlined as constants when used outside annotations.Pros
R2
is not packaged into the aar just likeR
.R
values using strings.@RClass
will output human friendly generated code in existing app projects.buck
andbazel
can generate their equivalent ofR2
very easily by implementing the logic on their own or by just reusingFinalRClassBuilder
(written entirely in java) from the gradle plugin module.Cons
R
values in the Android Studio/IntelliJ, they are kept in sync with the layout files etc. automatically by the IDE, butR2
is only generated after the gradle task that generatesR
. To keepR
andR2
in sync, the rename would need to be done twice once forR
and immediately inR2
. Without doing so, compilation would fail. This problem can be fixed easily by writing a custom intellij plugin that links the values inR2
andR
, similar to how the values inR
are linked to the layout xml files etc. This can be a future enhancement.