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

Annotations

Defines all the BindView annotation and the like that you use on fields and methods in view code. You can see them all here.

Annotation processor

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 XActivity_ViewBinding class.

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 BindingSet.Builder#addMethod.

Both these methods collect all the bindings into a BindingSet per type. Once a Type is processed, all these bindings are flushed to a JavaFile X_ViewBinding.java

See Jake Wharton’s talk on Annotation Processor to get more details of how one is built.

Runtime library

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.

The 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 R as 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 FinalRClassBuilder.


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.

Updated:

Comments