Butter Knife: View Binding Library for Android

This is the second instalment of my Code More Efficiently series and it directly targets Android programming, specifically dealing with UI and layouts. When I think Android and boilerplate, my thoughts immediately direct towards the UI elements and the ways in which they interact with each other as well as the entities hosting them, namely Activities and Fragments. Butter Knife, an open source library by Jake Wharton, allows the developer to fly past the boilerplate and focus on what matters most: The App!

Butterknife Logo

What is Butter Knife?

A butter knife is a blunt knife used for cutting or spreading butter … With that out of the way, let’s get to the real Butter Knife. Butter Knife is a View Binding library. What does that mean? It means that Butter Knife can

  • Find all the views in your layout and assign them to variables and/or fields that you have annotated within your code, essentially replacing all your calls to findViewById() with a single call to ButterKnife.bind().
  • Find each of the views whose ids are specified in an array and place it in a list of Views for you.
  • Associate various listeners with the widgets in your layout, such as
    • OnCLickListeners for single views or a list of views
    • OnLongClickListeners for single views or a list of views
    • OnFOcusChangedListeners
    • OnCheckedChangeListeners
    • OnItemSelectedListeners
    • OnItemClickListeners
    • OnItemLongClickListeners
    • … and more

Butter Knife does all of the above via simple annotations that you place on class fields and methods. Can you imagine how many less lines of code you’ll have to write?

Where can I use Butter Knife?

You can use Butter Knife’s view injection in Activities, Fragments, View holders for AdapterViews (such as ListView and GridView), View holders for RecyclerViews, and generally where there’s a View to be found!

View Binding

To annotate a view for injection, simply declare a field with a type that matches the view and annotate the field with @Bind. The only parameter required for the @Bind annotation is the id assigned to the view:

@Bind (R.id.my_image_view)
ImageView myImageView;

To indicate that the field may not be present in the layout, use any variant of the @Nullable annotation (e.g. Android support annotations):

@Nullable @Bind(R.id.my_image_view)
ImageView myImageView;

You may also add multiple views to a List of views

@Bind ({R.id.image_1,
               R.id.image_2,
               R.id.image_3})
List<ImageView> myImageViews;

or an array of Views

@Bind ({R.id.image_1,
               R.id.image_2,
               R.id.image_3})
ImageView[] myImageViews;

Once all your annotations are in place, all you need is a call to ButterKnife.bind() to do away with all those pesky findViewById() method calls. Depending on what you’re injecting the views into, you’ll use a different variant of ButterKnife.bind(). Don’t worry about it now, we’ll get to that shortly.

Binding Listeners

As previously mentioned, view injection is just one of the many awesome things that Butter Knife does for you. Another thing that Butter Knife is especially good at is injecting listeners. Listener injection is similar to view injection in the way that you still link the target and your code via annotations. The difference is in what you annotate. I’m sure you’ve already guessed that for listeners, because you’re responding to an event, you have to annotate a method. The semantics are identical, though. The annotations (depending on their type) take either a single view id or an array of view ids and connect a specific event to the annotated method. The same call to ButterKnife.bind() that processes your view injections will take care of your listeners as well so all you have to do is annotate. That’s easy, right?

Single Method Listeners

Many listener interfaces specify a single method to be implemented. For example the OnCLickListener only specifies the onClick (View view) method. While we’re talking about the OnClickListener interface, let’s have a look at how you’d assign the OnClick functionality to a button in your layout.

final Button button = (Button) findViewById (R.id.my_button);
button.setOnClickListener(new View.OnClickListener {
  public void onClick (View view) {
    // Process button click
  }
});

Now, I don’t know about you, but I have a few problems with the code above:

  • We wrote 6 lines of code that do nothing
  • Of those 6 lines, 3 are nothing but bloody boilerplate or brackets!
  • Unnecessary nesting of code

Using Butter Knife, all of the above can be reduced to a simple method in your class:

@OnCLick(R.id.my_button)
public void onClick (View view) {
    // Process button click
}

How does this code differ from the above? Well, we still have the same 3 lines for the method (I’m sure we all agree that regardless of the method body, the size of that method does not change between the two implementations). However, we’ve managed to get our boilerplate down from 3 lines to one. That’s for just one button. Imagine how many lines of bloody button boilerplate your can banish with Butter Knife for the entire layout or the entire app!

OnClickListeners

You already know this one from the last section, don’t you? Just annotate your method with @OnClick and pass in the id of the view whose clicks you want your method to handle.

@OnCLick(R.id.my_button)
public void buttonWasClicked () {
  // Process button click
}

Alternatively, if you need a reference to the view, you may add a View parameter to your method. Just keep in mind that nobody likes unused parameters, right?

@OnCLick(R.id.my_button)
public void buttonWasClicked (View view) {
  // Process button click
}

OnFocusChangedListener

Similar to OnClickListeners, Butter Knife can generate an OnFocusChangedListener for the target view and have it call your annotated method.

@OnFocusChange(R.id.my_edit)
public void inputFocusChanged (boolean hasFocus) {
  // true = your view gained focus
  // false = your view lost focus 
}

OnCheckedChangedListener

I think you get the idea by now, so I’ll jump straight to the code.

@OnCheckedChanged(R.id.my_chk)
public void checkboxToggled (boolean isChecked) {
  // true = checkbox is checked
  // false = checkbox is unchecked
}

Multi Method Listeners

Another group of listener interfaces (such as OnItemSelectedListener) define multiple methods. More often than not, though, you only have actual functionality in one method and the rest just sit uselessly as methods with empty bodies cluttering your code with their ugliness and distracting you with their uselessness. Well, Butter Knife allows you to implement only those methods that you’re actually implementing. If you’re implementing all the methods in the listener interface, you specify which with the callback parameter. If you don’t specify a callback along with the view id, Butter Knife assumes the main callback method. As for which method is the main one, I think for most listener interfaces it’s pretty obvious. If not, just try it!

OnItemSelectedListener

To automatically assign a method to handle your spinner item selections, use the @OnItemSelected annotation.

@OnItemSelected(R.id.my_spinner)
public void itemSelected (int position) {
  // you know which item is selected.
}

@OnItemSelected(value = R.id.my_spinner, 
        callback = NOTHING_SELECTED)
public void nadaSelected () {
  // nothing is selected.
}

OnItemSelectedListener Callbacks

As you would expect, the @OnItemSelected annotation has two callback values (to match the OnItemSelectedListener interface).

  • The ITEM_SELECTED callback connects with the onItemSelected(AdapterView, View, int, long) method. Your method only needs to include the int parameter denoting the selected index.
  • The NOTHING_SELECTED callback connects with the onNothingSelected(AdapterView) method. Your method does not need any input parameters

The default callback method for @OnItemSelected is ITEM_SELECTED.

OnPageChange

If you’re dealing with ViewPager, whether you’re using it for tabs or for flipping through other types of views, you need to know when the user has landed on a new page. Butter Knife allows you to inject an OnPageChangedListener. We’ve already covered how to specify non-default callbacks, so for the next couple of sections we’ll just stick to the default callback. To process the onPageSelected event of your ViewPager, implement a method that takes a single int parameter and annotate it with @OnPageChange. As discussed above, if you need to be informed of the other events, implement more methods accordingly and annotate them specifying the callback.

@OnPageChange(R.id.my_pager) 
public void pageSelected (int position) { 
  // page changed 
}

OnPageChange Callbacks

In order to match all the methods of the OnItemSelectedListener interface, the @OnPageChange annotation has 3 callback values.

  • The PAGE_SELECTED callback connects with the onPageSelected(int) method. Your method needs to include the int parameter denoting the selected page index.
  • The PAGE_SCROLL_STATE_CHANGED callback connects with the onPageScrollStateChanged(int) method.
  • The PAGE_SCROLLED callback connects with the onPageScrolled(int, float, int) method.

The default callback method for @OnPageChange is PAGE_SELECTED.

OnTextChanged

Something that you see often in modern apps is their ability to process your input as you type. For example, in Google Maps, as you type “van”, you are prompted with “Vancouver, BC” (or whatever matches nearby). This is done via a TextWatcher interface, which informs you of every change to the TextEdit before, during, and after the change. Of course, as with many other multi method listeners, you really only care about one of these methods: during. Butter Knife provides the @OnTextChanged annotation to attach a TextWatcher to an EditText.

@OnTextChanged(R.id.my_edit)
public void textChanged (CharSequence text) {
  // text changed
}

OnTextChanged Callbacks

In order to match all the methods of the TextWatcher interface, the @OnTextChanged annotation has 3 callback values.

  • The TEXT_CHANGED callback connects with the onTextChanged(CharSequence, int, int, int) method.
  • The AFTER_TEXT_CHANGED callback connects with the afterTextChanged(Editable) method.
  • The BEFORE_TEXT_CHANGED callback connects with the beforeTextChanged(CharSequence, int, int, int) method.

The default callback method for  @OnTextChanged is TEXT_CHANGED.

Injection Sites

OK, so you know everything you need to start using Butter Knife. What remains is how to get Butter Knife to do its thing and that depends on where you’re using Butter Knife. The basic idea is the same from Activity to Fragments, View holders, and so on. I’ll only cover the basic ‘injection sites’, which are the ones you’ll be using most often, but you can easily apply the learnings to your specific usage.

Injection in Activity

The ideal place to call ButterKnife.bind() in an Activity is within your onCreate (Bundle) method after you setContentView (), but before you reference any of the views. This is essentially where your very first call to findViewById() is. Now you can tell all those findViewByIds to get lost.

Before

public class MyActivity extends Activity {
  ImageView icon;
  Button btnOne;
  Button btnTwo;
  EditText name;

  protected void onCreate (Bundle savedState) {
    setContentView(R.id.activity_main);
    icon = (ImageView)findViewById(R.id.icon);
    btnOne = (Button)findViewById(R.id.button1);
    btnTwo = (Button)findViewById(R.id.button2);
    name = (Button)findViewById(R.id.edit);
  
    // Now the listeners ... will this ever end?
    btnOne.setOnClickListener (new View.OnClickListener () {
      public void onClick (View view) {
        // button one clicked
      } 
    });

    btnTwo.setOnClickListener (new View.OnClickListener () { 
      public void onClick (View view) {
        // button two clicked
      } 
    });

    edit.addTextChangedListener (new TextWatcher () {
      public void onTextChanged (CharSequence s, int start, int before, int count) {
        // text changed
      }

      public void beforeTextChanged (CharSequence s, int start, int count, int after) {
        // bloody boilerplate bloat
      }

      public void afterTextChanged (Editable s) {
        // bloody boilerplate bloat
      }
    });
  }
}

After

public class MyActivity extends Activity {
  @InjectView(R.id.icon) ImageView icon;
  @InjectView(R.id.edit) EditText name;
  
  @Override
  protected void onCreate (Bundle savedState) {
    setContentView(R.id.activity_main);
    ButterKnife.inject(this);
    // et viola!
  }

  @OnTextChanged(R.id.edit)
  public void textChanged (CharSequence text) {
    // text changed
  }

  @OnClick(R.id.button1)
  public void buttonOneClicked () {
    // button one clicked
  }

  @OnClick(R.id.button2)
  public void buttonTwoClicked () {
    // button two clicked
  }
}

Check those line numbers … in only 25 lines, we’ve accomplished identical functionality that we initially produced with 41 lines. Now while lines of code isn’t exactly the best measure of assessing code efficiency, you’re going to have difficulty convincing me that the before section above is preferred over the after.

Injection in Fragments

What do we want? ButterKnife.bind()! When do we want it? onViewCreated(). Ok, I admit that was bad, but it got the point across. The idea here is the same: once you have your root view, you get Butter Knife to ‘inject’ the inner views into your Fragment. The ideal place to do this is inside your onViewCreated(View, Bundle) method. There is nothing stopping you from initiating the injection in onCreateView(LayoutInflater, ViewGroup, Bundle), but I personally prefer the former as it indicates that the provided view is now attached to your Fragment. The only other step that you have to take with Fragments is to ‘reset’ Butter Knife when your Fragment’s view has been destroyed. To do that, you simply call ButterKnife.unbind (this) within your onDestroyView() method. I won’t be doing a before & after comparison in this section and the next because my work is done if the last one convinced you, and if it didn’t, well … let’s agree to disagree. A code snippet says a thousand words, so here’s some code that performs the injection immediately after creating its root view:

public class MyFragment extends Fragment {
  @InjectView(R.id.icon) ImageView icon;
  @InjectView(R.id.edit) EditText name;

  @Override
  public View onCreateView(LayoutInflater inflater,
                           ViewGroup container, 
                           Bundle savedState) {
    View view = inflater.inflate(R.layout.my_fragment,
                                 container, 
                                 false);
    ButterKnife.inject(this, view);
    // et viola!
    return view;
  }

  @OnTextChanged(R.id.edit)
  public void textChanged (CharSequence text) {
    // text changed
  }

  @OnClick(R.id.button1)
  public void buttonOneClicked () {
    // button one clicked
  }

  @OnClick(R.id.button2)
  public void buttonTwoClicked () {
    // button two clicked
  }
 
  @Override public void onDestroyView() {
    super.onDestroyView();
    ButterKnife.reset(this);
  }
}

And here’s the same code, with injection happening after the root view has attached to the fragment:

public class MyFragment extends Fragment {
  @InjectView(R.id.icon) ImageView icon;
  @InjectView(R.id.edit) EditText name;

  @Override
  public View onCreateView(LayoutInflater inflater,
                           ViewGroup container, 
                           Bundle savedState) {
    return inflater.inflate(R.layout.my_fragment,
                            container, 
                            false);
  }
 
  @Override 
  public void onViewCreated(View view, 
                            Bundle savedState) {
    ButterKnife.inject(this, view);
    // et viola!
  }

  @OnTextChanged(R.id.edit)
  public void textChanged (CharSequence text) {
    // text changed
  }

  @OnClick(R.id.button1)
  public void buttonOneClicked () {
    // button one clicked
  }
 
  @OnClick(R.id.button2)
  public void buttonTwoClicked () {
    // button two clicked
  }
 
  @Override public void onDestroyView() {
    super.onDestroyView();
    ButterKnife.reset(this);
  }
}

public class MyFragment extends Fragment {
 @InjectView(R.id.icon) ImageView icon;
 @InjectView(R.id.edit) EditText name;

 @Override 
 public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
 return inflater.inflate(R.layout.my_fragment, container, false);
 }
 
 @OnTextChanged(R.id.edit)
 public void textChanged (CharSequence text) {
 // text changed
 }

 @OnClick(R.id.button1)
 public void buttonOneClicked () {
 // button one clicked
 }

 @OnClick(R.id.button2)
 public void buttonTwoClicked () {
 // button two clicked
 }
 
 @Override public void onDestroyView() {
 super.onDestroyView();
 ButterKnife.reset(this);
 }
}

Injection in View holders

If your lists don’t scroll in a buttery smooth manner, chances are you’re not using View holders. For those of you who don’t know what View holders are (watch this video) View holders are regular java classes that keep a reference to the sub-views inside your item views in an AdapterView so that you can immediately bind your data to the view. Once the view holder is initialized with a View, it is set as the view’s tag. When using the setTag method to assign your view holders try to avoid setTag (int, Object) method (it leaks).

Let’s start with the View holder itself (remember if you’re declaring your view holder as a nested class, it has to be static):

public class ListRow { 
  @InjectView (R.id.title) TextView title;
  @InjectView (R.id.icon) ImageView icon; 
  @InjectView (R.id.summary) TextView summary; 

  public ListRow (final View view) { 
    Butterknife.inject(this, view); 
  }
}

Now use this view holder inside your adapter as you normally would:

public class MyAdapter extends BaseAdapter {
  private final LayoutInflater inflater;
  private Data[] data;

  public MyAdapter (final Context context) {
    super();
    inflater = LayoutInflater.from (context);
  }

  // yada yada getCount getItem getItemId blah
  @Override public View getView (int position,
                                 View recycle,
                                 ViewGroup parent) {
    final View view = null == recycle
                      ? newView (parent)
                      : recycle;
    final ListRow row = (ListRow) view.getTag ();
    final Data item = getItem (position);
    row.title.setText (item.title);
    row.summary.setText (item.summary);
    row.icon.setImageResource (item.iconRes);
    return view;
  }

  private View newView (final ViewGroup parent) {
    final View view = 
      inflater.inflate (R.layout.item_list,
                        parent,
                        false);
      view.setTag (new ListRow (view));
      return view;
  }
}

Actions & Setters

Butter Knife’s Actions and Setters allow you to specify simple behaviours that you can then apply to an entire list of views at once. There’s even support for applying view properties (e.g. View.ALPHA) to all views in a list. Actions and Setters do not require injection, but then again, do you know an easier way of creating a list of views? A few examples appear below. For those examples, let’s assume that the following view list injection exists and has been injected:

@InjectViews ({R.id.image_1,
               R.id.image_2,
               R.id.image_3})
List<ImageView> images;

Setting View Properties

Let’s change the View.ALPHA property on all of these ImageViews to 50% transparet:

ButterKnife.apply (images, View.ALPHA, 0.5f);

Actions

Here’s we define Actions to enable and disable these views:

static final Action<View> DISABLE = new Action<>() {
  @Override 
  public void apply(View view, int index) {
    view.setEnabled(false);
  }
}

static final Action<View> ENABLE = new Action<>() {
  @Override 
  public void apply(View view, int index) {
    view.setEnabled(true);
  }
}

To use these actions, or apply them to the views:

// to disable them all
ButterKnife.apply (images, DISABLE);
// or to enable them all
ButterKnife.apply (images, ENABLE);

Setters

Setters are very similar to actions. The only difference is that you specify a value to be set. Let’s take the two actions from the previous section and implement a setter instead:

static final Setter<View, Boolean> ENABLED = new Setter<>() {
  @Override
  public void set(View view,
                  Boolean value,
                  int index) {
    view.setEnabled(value);
  }
}

To apply this setting to our views:

// to disable them all
ButterKnife.apply (images, ENABLED, false);
// or to enable them all
ButterKnife.apply (images, ENABLED, true);

That’s pretty neat, isn’t it? On that note, we’ve arrived at the conclusion of this segment of Coding More Efficiently.

Using Butter Knife

If you’re using Android Studio/Gradle, you can easily add Butter Knife to your dependencies:
compile 'com.jakewharton:butterknife:7.0.1'
Otherwise, to download the latest Butter Knife JAR, visit the Butter Knife Github portal.

Slides & Code

octocat You can find my Butter Knife demo code on Github
I have also prepared a few slides for this segment as well.