Butterknife Android Example [Complete Tutorial]

Spread knowledge

butterknife_android_example
ButterKnife Library

As developers we always aim to remove all kinds of boilerplate code from our applications. But with Android application sometimes  we are forced to do similar view initialization in almost every view components (Activities, Fragments etc). Here I am talking about initialization with findViewById() , setOnClickListener(), setOnTouchListener which is mandatory to be repeated for all the views we are working with. Fortunately, Jake Wharton from Square has come with ButterKnife Library which uses annotation processing to removes all the unnecessary, repetetive code from your application. Before beginning with our Butterknife android example let us first understand how this library works.

What is Android Butterknife ?

Butterknife is a lightweight view binding library to simplify finding views and attaching listeners by binding them with annotations in Android components like Activity, Fragment or Dialog. Here annotation processing is used at compile time which automatically generates new simple Java classes containing logic to handle view initialization. Basically  at compile time annotation processor scans through all your files looking for annotations and processes them to creating new Java files. Following are some of the advantages of use Butterknife library in your application

  • Removes repetitive findViewById calls from your code by using @BindView directly  on fields.
  • Removes all the anonymous inner classes created to handle events like clicks with setOnClickListener(). Instead we append  @OnClick annotation with methods which handle the action. 
  • Removes the resource lookups by using resource annotations on fields

In the following Butterknife Android example we will explore the uses of Butterknife Library by implementing various annotations

Adding Gradle Dependencies

Before beginning with the implementation of Butterknife android example we need to add the following dependencies in build.gradle

dependencies {
  implementation 'com.jakewharton:butterknife:9.0.0-rc1'
  annotationProcessor 'com.jakewharton:butterknife-compiler:9.0.0-rc1'
}

Initializing  Butterknife

View binding with Butterknife can be performed in an activity, view or even dialog. But before adding annotations we need to initialize Butterknife library. We initialize it by specifying a target component to search for annotations and root view to search views.

Below are some of the APIs used to specify the target component and rootview

  • bind(android.app.Activity target)
    Binds annotated fields and methods in the specified Activity.
  • bind(android.app.Dialog target)
    Binds annotated fields and methods in the specified Dialog.
  • bind(java.lang.Object target, android.view.View source)
    Binds annotated fields and methods in the specified target using the source View as the view root.
  • bind(android.view.View target)
    Binds annotated fields and methods in the specified View.

Below code snippet shows how to initialize ButterKnife in your Activity.

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        /*
        Below call initializes Butterknife library to find and process annotated fields and
        methods in the current activity. Current activity layout is used as the view root.
         */
        ButterKnife.bind(this);
    }
}

Similarly we can initialize Butterknife in a Fragment. We just need to pass the extra method paramater “view”  representing the rootview.

public class ExampleFragment extends Fragment {

    public View onCreateView(LayoutInflater layoutInflater, ViewGroup parent, Bundle bundle) {
        View view = layoutInflater.inflate(R.layout.fragment_example,parent,false);
        /*
        Below call initializes ButterKnife library to find the annotated field and methods in the 
        current class and set the variable view as the root view
         */
        ButterKnife.bind(this,view);
        return view;
    }

}

Apart from Activity and Fragment, you can also use Butterknife in other components  like ViewHolder, Adapters etc. Below example shows how to initialize butter knife in an Adapter’s getView()

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    View v = LayoutInflater.from(context).inflate(R.layout.item_spinner, null);
    ButterKnife.bind(this,v);

    return v;
}

Similarly we can initialize Butterknife for custom views, dialogs etc

Initializing Views

Initializing views is the first thing we do in our Android component. Whether its a Activity or Fragment first several lines of our code is always occupied by multiple findViewById() calls to initialize various views inside our layout. Consider the example below, here we initialize and attach listeners to all the four views as soon as the activity is created.

public class MainActivity extends AppCompatActivity {

    Button buttonOne;

    Button buttonTwo;

    TextView textOne;

    TextView textTwo;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        buttonOne = findViewById(R.id.button_one);
        buttonTwo = findViewById(R.id.button_two);
        textOne = findViewById(R.id.text_view_one);
        textTwo = findViewById(R.id.text_view_two);

        buttonOne.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.d("AndroidClarified","Button One Clicked");
            }
        });

        buttonTwo.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.d("AndroidClarified","Button Two Clicked");
            }
        });
    }
}

@BindView, @OnClick

Now as the number of views increase in our layout so does the above code. For every new view we need to initialize using findViewById() and then add a anonymous class if we need to listen for any events. This approach is tiring and adds a lot of boilerplate code in our application.  Now for the same views we use Butterknife library to initialize and attach click listeners. Look at the code snippet below. The same is achieved with lesser code which is also easy to understand.

public class MainActivity extends AppCompatActivity {

    /*
    @BindView annotation is used to initialize views
     */
    @BindView(R.id.button_one)
    Button buttonOne;

    @BindView(R.id.button_one)
    Button buttonTwo;

    @BindView(R.id.text_view_one)
    TextView textOne;

    @BindView(R.id.text_view_two)
    TextView textTwo;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        /*
        Below call initializes Butterknife library to find and process annotated fields and
        methods in the current activity. Current activity layout is used as the view root.
         */
        ButterKnife.bind(this);

    }

    /*
       @OnClick annotation denoted that the respective method will be invoked on click of 
       the view
     */
    @OnClick(R.id.button_one)
    public void buttonOneClicked() {
        Log.d("AndroidClarified","Button One Clicked");
    }
    
    @OnClick(R.id.button_two)
    public void buttonTwoClicked(){
        Log.d("AndroidClarified","Button Two Clicked");
    }
}
  • As you must have noticed, we use @BindView annotation to initialize the view. Adding that annotation casts the view with the specified id from the root view to the field.
  • Similarly @OnClick annotation automatically attaches a listener and will invoke the respective method on click of the  view i.e. Clicking of button one (with id R.id.button_one) will invoke method buttonOneClicked()
  • Any number of parameters from OnClickListener#onClick(android.view.View) may be used with the
    method. This means you can either have no arguments, as in this example, or specify View as the argument.
  • If multiple buttons have the same action you can specify multiple ids with @OnClick annotation as shown below.
     
     @OnClick({R.id.button_one,R.id.button_two})
    public void buttonOneClicked() {
        Log.d("AndroidClarified","Buttons Clicked");
    }
    
  • Note that all the views binded with annotation should be present in the target layout. If not an exception is encountered. To avoid an exception you can add @Nullable annotation to the binding. This prevents from the exception being thrown if the view is not found. Look at the code snippet below for reference
    @Nullable
    @BindView(R.id.text_view_two)
    TextView textTwo;

@OnTouch, @OnLongClick

  • Similar to @OnClick we have have annotations like @OnTouch, @OnLongClick etc which help us to handle various events on the view. As the name suggests, @OnTouch is to handle touch events and @OnLongClick to handle long click events. One important thing to note is the number of arguments you are adding on the method with annotation.
  • @OnTouch annotation will bind the method to OnTouchListener#onTouch(View, MotionEvent) listener of the view for each id specified. Both method arguments of the listener, View and MotionEvent , are optional but it has to always return boolean
    @OnTouch(R.id.text_view_one)
    boolean onTouch() {
        Toast.makeText(this, "Touched!", Toast.LENGTH_SHORT).show();
        return false;
    }
  • @OnLongClick annotation will bind the method to  OnLongClickListener#onLongClick(View) listener of the view for each id specified. No method argument is mandatory but it has to always return boolean.
    @OnLongClick(R.id.text_view_two)
    boolean onLongClick() {
        Toast.makeText(this, "On Long Clicked!", Toast.LENGTH_SHORT).show();
        return false;
    
    }

@OnCheckedChange, @OnItemSelected , @OnItemClick

  • Butterknife even provides us with annotations to handle events on custom views like CompoundButton and AdapterView(Spinner, ListView) . Similar to other annotations this also needs to be a binded to a method along with the id of the view.
  • @OnCheckedChange annotation will bind the method to the OnCheckedChangeListener(CompoundButton,boolean) of the view for each id specified. The only mandatory argument for the binded method is boolean. You can add a CompoundButton  argument as per your use case.
    @OnCheckedChanged(R.id.checker)
       void onChecked(boolean checked) {
           Toast.makeText(this, checked ? "Checked!" : "Unchecked!", Toast.LENGTH_SHORT).show();
       }
  • @OnItemSelected annotation will bind the method to OnItemSelectedListener#onItemSelected(AdapterView, View, int,long) of the view of each of the specified. This works very well with ListViews, Spinner etc. The only mandatory method argument for the binded method is integer position.
    @OnItemSelected(R.id.example_list) 
    void onItemSelected(int position) {
        Toast.makeText(this, "Selected position " + position + "!", Toast.LENGTH_SHORT).show();
     }

Important thing to note is with Butterknife library we dont we need to initialize view with @BindView before attaching listener. You can directly bind a method to any listener using @OnClick, @OnTouch etc and the it will be invoked

Binding resources

In the above section we discussed how to bind fields to views and methods to view event listeners using Butterknife annotations. Similarly we can bind fields to android resources using different Butterknife annotations. Here resources can be anything like drawables, string , dimension ,color code, arrays etc. This will remove all the boilerplate code and save a lot of developer effort. Also its super easy to implement, you just need to bind the resources using the respective annotation and providing the resource id. The code snippet below is self explanatory

@BindColor(R.color.colorPrimary)
int primaryColor;

@BindDimen(R.dimen.dimen_9dp)
int myDimen;

@BindString(R.string.app_name)
String appName;

@BindDrawable(R.drawable.ic_launcher_background)
Drawable bgDrawable;

/*
@BindBitmap directly binds a drawable image into a Bitmap field.
 */
@BindBitmap(R.drawable.statue)
Bitmap bitmap;
  • Same as it is for @BindView , @OnClick etc it is important to initialize ButterKnife for the resource binding to work

Conclusion

This finishes our Butterknife Android example. I hope you are now clear with its implementation and advantages. If not feel free ask any question in the comments below. Similar to Butterknife, there is one more very important library which helps you upload images from your application. Read more about it here .


Spread knowledge

Leave a Reply

Your email address will not be published. Required fields are marked *