How ButterKnife actually works?

You all know ButterKnife: the brilliant annotation processing library to bind views and methods for Android by @JakeWharton

But, how does it work? Most people think by just adding new methods to our classes, saving us a quite a lot of boiler plate code.

Well, it’s definitely saving us some code, but you may be surprised to discover that is not changing our classes at all.

Want to find out? First, you need a quick overview of how annotation processing works in java.

Java Annotation Processing

Annotation processing is a tool build in javac for scanning and processing annotations at compile time.

You can define your own annotations and a custom processor to handle them.

Java Compiler

At OpenJDK you can read an excellent overview of how Java compiler works.

This chart summarizes very well the part that interests us:

java compiler Java compiling process overview

ButterKnife workflow

When you compile your Android project ButterKnife Annotations Processor process method is executed, doing the following:

Sample

For the sample code you can find at https://github.com/JakeWharton/butterknife this is what happens underneath:

butterknife sample

ExampleActivity.java

class ExampleActivity extends Activity {
  @FindView(R.id.user) EditText username;
  @FindView(R.id.pass) EditText password;

  @OnClick(R.id.submit) void submit() {
    // TODO call server...
  }

  @Override public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.simple_activity);
    ButterKnife.bind(this);
    // TODO Use fields...
  }
}

During compile time this java class will be generated:

ExampleActivity$$ViewBinder.java

public class ExampleActivity$$ViewBinder<T extends com.lgvalle.samples.ui.ExampleActivity> implements ViewBinder<T> {
  @Override public void bind(final Finder finder, final T target, Object source) {
    View view;
    view = finder.findRequiredView(source, 2131361865, "field 'user'");
    target.username = finder.castView(view, 2131361865, "field 'user'");
    view = finder.findRequiredView(source, 2131361868, "field 'pass'");
    target.password = finder.castView(view, 2131361868, "field 'pass'");

    view = finder.findRequiredView(source, 2131361874, "field 'submit' and method 'submit'");
    view.setOnClickListener(
      new butterknife.internal.DebouncingOnClickListener() {
        @Override public void doClick(android.view.View p0) {
          target.submit();
        }
      });
  }

  @Override public void reset(T target) {
    target.username = null;
    target.password = null;
  }
}

Then, during execution time, when we call ButterKnife.bind(this); what happens is:

This is why annotated attributes and methods must be public: ButterKnife needs to be able to access them from a separate class.

More info

If you want to know more about Java Annotation Processing, this three post help me out a lot when writing this post: