Start Spreading

Butter Knife has become one of the premiere (I use this term lightly to mean that I really, really, REALLY like it) 3rd party Android libraries. For those who have not experienced Butter Knife, it is a way to inject views, layouts, colors, drawables, strings, as well as the many types of view events possible. What makes this awesome is that there is no gotcha. These injections can happen in Activities, Fragments, and any other class types (not interfaces).

Why You Should Be Using Butter Knife

There are a few reason why every Android Developer should begin to refactor their code to include the Butter Knife library. We will cover a few of these reasons below.

Reason 1: Everybody likes clean code (this is relative of course)

Sometimes initialization is messy. First we declare globally any code that we want to use within a particular scope and then we initialize this code. Within Android, this means "muddying" up the onCreate method of activities or the onCreateView of fragments with initializations instead of startup logic. Take the code fragment below:

...

TextView mUsernameTextView;
Button mRegisterButton;

//any other initializations
...

public void onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
 View view = inflater.inflate(R.layout.fragment_username_choice_screen, container, false);

 mUsernameTextView = (TextView) view.findViewById(R.id.username);
 mRegisterButton = (Button) view.findViewById(R.id.register_button);

 mRegisterButton.setOnClickListener(new View.OnClickListener() {
 @Override
 public void onClick(View v) {
 //some logic
 ...
 }
 });
 return view;
}

This is a very simple example, but imagine if there were 10 or more view components that needed to be initialized and how much of a nightmare that would be. With Butter Knife, this becomes more simple. Checkout the refactored code of the example above:

...

@Bind(R.id.username)
TextView mUsernameTextView;

@Bind(R.id.register_button)
Button mRegisterButton;

//any other initializations
...

public void onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
 View view = inflater.inflate(R.layout.fragment_username_choice_screen, container, false);

 ButterKnife.bind(this);

 mRegisterButton.setOnClickListener(new View.OnClickListener() {
 @Override
 public void onClick(View v) {
 //some logic
 ...
 }
 });
 return view;
}

A lot less going on, which allows the onCreateView method to be more of a logic initializer than a variable initializer, which results in cleaner code.

However, we can take this example even further. This brings us to the next reason why every Android developer should be using Butter Knife.

Reason 2: Allows for Easier Implementation of Separation of Concerns

Android allows developers to set onClick listeners within the layout declaration. Consider the button we have been using in the previous examples. We can separate our concerns by setting the onClick listener in the layout like this:



This would then allow our code above to become a little bit more readable because we separated our concerns:
...

@Bind(R.id.username)
TextView mUsernameTextView;

@Bind(R.id.register_button)
Button mRegisterButton;

//any other initializations
...

public void onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
 View view = inflater.inflate(R.layout.fragment_username_choice_screen, container, false);

 ButterKnife.bind(this);

 return view;
}

//All Event Handlers Below:
public void doRegistration(View view) {
 //some logic
}

There are a few reasons why even though this is legal and often done, it is not a good idea.
  1. You have to go back and look at the layout if there is an issue with the onClick handler not working.
  2. It's not really clear for the next developer looking at your code as to "who" or where this method is being called from (Yes, you can put that in the comment block, but that still brings us back to point 1).
  3. With code and layout changes inevitable, having to handle both setting listeners in the layout as well as in the code can be troublesome (once again think about things beyond this simple example. For instance, having lots of onClick listeners!).

This is where Butter Knife can help again. Butter Knife not only allows for a good separation of concerns approach, but also allows for an intuitive reading by other developers of that approach. The above code then looks like this:

...

@Bind(R.id.username)
TextView mUsernameTextView;

//any other initializations
...

public void onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
 View view = inflater.inflate(R.layout.fragment_username_choice_screen, container, false);

 ButterKnife.bind(this);

 return view;
}

//All Event Handlers Below:
@OnClick(R.id.register_button)
public void doRegistration(View view) {
 //some logic
}

Now we know from first glance at the code which button is linked to the doRegistration onClick listener. Also notice, that we don't need to bind to the button in order to set it's listener (thus, cleaner code and more intuitive separation of concerns).

Reason 3: Abstraction Becomes a little Easier:

If you have been doing Android development for some time, you find it much easier to leave interface definitions within your code instead of being a "good developer" and extracting them out into their own classes (many reasons you should, mainly the possibility of code reuse as well as simplifying testing). This is because developers either have to create really messy constructors that take in 7, 8, 9 different component parameters or have to re-instantiate each component in order to perform some type of logic in the interface definition.

This is pretty common as things like Tab.OnTabSelectedListener are used to handle what happens when a particular tab is clicked within a TabLayout.

Let's say that we have a custom ToolBar component that has a title within it. This ToolBar component is built to set the title of the currently selected tab. This tab also has a relationship with a ViewPager for tab gesture interactions. Each tab title is contained in the strings file for i8n purposes and for good separation of concern coding.

As you can imagine this would be difficult to manage. Let's choose the simplest route of just passing in the calling Activity into the class and see what this may look like without Butter Knife:

public final class TabSelectedListener implements TabLayout.OnTabSelectedListener {

 @Bind(R.id.sceneTitle)
 TextView mTitleView;

 @Bind(R.id.container)
 ViewPager mViewPager;

 Activity callingActivity;

 private TheSceneTabSelectedListener(){}

 public TheSceneTabSelectedListener(Activity callingActivity) {

 this.callingActivity = callingActivity;
 mTitleView = (TextView)this.callingActivity.findViewById(R.id.sceneTitle);
 mViewPager = (ViewPager)this.callingActivity.findViewById(R.id.container);

 }
 @Override
 public void onTabSelected(TabLayout.Tab tab) {

 mViewPager.setCurrentItem(tab.getPosition());

 final int position = tab.getPosition();

 switch (position) {
 case 0:
 mTitleView.setText(this.callingActivity.getString(R.string.tab_home));
 break;
 case 1:
 mTitleView.setText(this.callingActivity.getString(R.string.tab_discover));
 break;
 case 2:
 mTitleView.setText(this.callingActivity.getString(R.string.tab_messages));
 break;
 case 3:
 mTitleView.setText(this.callingActivity.getString(R.string.tab_connections));
 break;
 case 4:
 mTitleView.setText(this.callingActivity.getString(R.string.tab_favorites));
 }

 }

 //other interface definitions
 ...
}

That's a lot of getString methods and re-initialization of code that was already set in the calling activity/fragment. Let's see how Butter Knife takes care of this:

public final class TabSelectedListener implements TabLayout.OnTabSelectedListener {

 @BindString(R.string.tab_home)
 String tabHome;

 @BindString(R.string.tab_discover)
 String tabDiscover;

 @BindString(R.string.tab_messages)
 String tabMessages;

 @BindString(R.string.tab_connections)
 String tabConnections;

 @BindString(R.string.tab_favorites)
 String tabFavorites;

 @Bind(R.id.sceneTitle)
 TextView mTitleView;

 @Bind(R.id.container)
 ViewPager mViewPager;

 private TheSceneTabSelectedListener(){}

 public TheSceneTabSelectedListener(Activity callingActivity) {
 ButterKnife.bind(this, callingActivity);
 }
 @Override
 public void onTabSelected(TabLayout.Tab tab) {
 mViewPager.setCurrentItem(tab.getPosition());

 final int position = tab.getPosition();

 switch (position) {
 case 0:
 mTitleView.setText(tabHome);
 break;
 case 1:
 mTitleView.setText(tabDiscover);
 break;
 case 2:
 mTitleView.setText(tabMessages);
 break;
 case 3:
 mTitleView.setText(tabConnections);
 break;
 case 4:
 mTitleView.setText(tabFavorites);
 }
 }

 //other interface definitions
 ...
}

Because Butter Knife also allows you to define bindings for various standard variable types, retrieving strings from the string file no longer has to consist of messy getString declarations. Here we initialize everything in one place and make the logic a little more leaner and easier to read.

Conclusion

Butter Knife is a pretty impressive library that allows for developers to manage their code a lot easier. It helps to enforce some standards that if we are honest, we often don't adhere to, or are otherwise too lazy to implement.

This is only the tip of the Iceberg when it comes to what is possible with this library. So if you care about your code, and the developers that come after you, make Butter Knife a necessary tool of your Android development.