As an Android developer, you’re surely heard of Butterknife, may be even used it in countless projects yourself. Have you ever wondered what is behind the magical API of Butterknife. In this post, we unveil the magic and deconstruct its code.
Butterknife is majorly composed of three pieces
Defines all the
BindView annotation and the like that you use on fields and methods in view code. You can see them all here.
This is the most interesting part of Butterknife. The implementation is simple(ish). The processor says it’s interested in all the Butterknife annotations. Then the compiler calls the processor with all the elements annotated with these annotations. It creates the
Here is a sample _ViewBinding class generated by the processor.
Let’s focus on the
BindView annotation, others behave similarly (listeners are slightly different). The crux of
parseBindView is in lines 475-499.
Listeners are implemented with little more complexity, mainly in
parseListenerAnnotation, specifically lines 1139-1164 and 1204-1213. If we skip all the error-checking, the method
parseListenerAnnotation matches the parameters from the Android listener which are used in the bound method and passes off the required params to
Both these methods collect all the bindings into a
BindingSet per type. Once a
Type is processed, all these bindings are flushed to a
See Jake Wharton’s talk on Annotation Processor to get more details of how one is built.
This is a utility library and some sugar. The utilities are used from the generated code and the
Butterknife class. When you call
Butterknife.bind(), it reflectively loads the processor-generated class, which is named
your-view-class-name + "_ViewBinding" and constructs an instance of the class.
Butterknife class is not really required to make use of the binder. In fact, it adds runtime reflection cost. You can instead use the generated class directly. This is the approach Dagger 2 has taken as well.
Bonus: The Gradle plugin
The gradle plugin has a very simple function. It lets you use Butterknife in library modules. But why can’t you use Butterknife in library modules like regular application modules? Because the element value in an annotation can only be a constant value for primitive types12. But the
R class generated by Android Gradle plugin doesn’t make the ID values constant in library modules. If libraries had their resource IDs final, then the IDs could collide when building the final APK. So your compile step will complain loudly if you write
@BindView(R.id.viewid) View idView;
You could not use Butterknife in library modules earlier, that is until Gautam Korlam contributed a Gradle plugin which clones
R2 with final values for resources. Now you can use
@BindView(R2.id.viewid) View idView in library modules without worry. Most of the interesting work is done in the class
I hope you’re now comfortable venturing into the source code of Butterknife armed with this knowledge.
Tip: Install Insight.io for Github to add IDE-like navigation capabilities to Github for pleasant code reading.
Look out for the next post where we would be building our own version of Butterknife from scratch.
Thanks to Yashasvi Girdhar for reading drafts of the post.