-
Notifications
You must be signed in to change notification settings - Fork 6.3k
Reducing View Boilerplate with Butterknife
Attention: Butter Knife is now deprecated. Please switch to view binding. Existing versions will continue to work, obviously, but only critical bug fixes for integration with AGP will be considered. Feature development and general bug fixes have stopped.
Butterknife is a popular View "injection" library for Android. This means that the library writes common boilerplate view code for you based on annotations to save you time and significantly reduce the lines of boilerplate code written.
This guide covers the most common usages of the library.
Butterknife uses compile-time annotations which means there is no additional cost at run-time. Instead of slow reflection, code is generated ahead of time. Calling bind delegates to this generated code that you can see and debug. This means that Butterknife does not slow down your app at all!
Butterknife is about reducing view boilerplate. Dagger 2 is dependency injection for arbitrary components. Dagger is very flexible but is not intended for view injection. Think of Butter Knife as a means of binding views rather than injection. In other words, Butterknife and Dagger 2 are complementary and many projects include both for different purposes.
Add the following to app/build.gradle
file:
dependencies {
implementation 'com.jakewharton:butterknife:8.4.0'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.4.0'
}
Make sure to upgrade to the latest Gradle version to use the annotationProcessor
syntax.
Use gradleplease to get the latest version. See this page for alternate installation methods.
There are three major features of ButterKnife:
- Improved View Lookups
- Improved Listener Attachments
- Improved Resource Lookups
Eliminate findViewById
calls by using @BindView
on fields:
class ExampleActivity extends Activity {
// Automatically finds each field by the specified ID.
@BindView(R.id.title) TextView title;
@BindView(R.id.subtitle) TextView subtitle;
@BindView(R.id.footer) TextView footer;
@Override public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.simple_activity);
ButterKnife.bind(this);
// TODO Use fields...
}
}
This can be done within Activity
, Fragment
, or Adapter
classes. For example, fragment usage would look like:
public class FancyFragment extends Fragment {
@BindView(R.id.button1) Button button1;
@BindView(R.id.button2) Button button2;
private Unbinder unbinder;
@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fancy_fragment, container, false);
unbinder = ButterKnife.bind(this, view);
// TODO Use fields...
return view;
}
// When binding a fragment in onCreateView, set the views to null in onDestroyView.
// ButterKnife returns an Unbinder on the initial binding that has an unbind method to do this automatically.
@Override public void onDestroyView() {
super.onDestroyView();
unbinder.unbind();
}
}
Within a ViewHolder
inside of a ListView
adapter:
public class MyAdapter extends BaseAdapter {
@Override public View getView(int position, View view, ViewGroup parent) {
ViewHolder holder;
if (view != null) {
holder = (ViewHolder) view.getTag();
} else {
view = inflater.inflate(R.layout.whatever, parent, false);
holder = new ViewHolder(view);
view.setTag(holder);
}
holder.name.setText("John Doe");
// etc...
return view;
}
static class ViewHolder {
@BindView(R.id.title) TextView name;
@BindView(R.id.job_title) TextView jobTitle;
public ViewHolder(View view) {
ButterKnife.bind(this, view);
}
}
}
This will save you the need to ever write findViewById
ever again!
Eliminate anonymous inner-classes for listeners by annotating methods with @OnClick
and others:
@OnClick(R.id.submit)
public void sayHi(Button button) {
button.setText("Hello!");
}
We can attach multiple views to the same listener with:
@OnClick({ R.id.door1, R.id.door2, R.id.door3 })
public void pickDoor(DoorView door) {
if (door.hasPrizeBehind()) {
Toast.makeText(this, "You win!", LENGTH_SHORT).show();
} else {
Toast.makeText(this, "Try again", LENGTH_SHORT).show();
}
}
The following event listeners are supported out of the box: OnClick
, OnLongClick
, OnEditorAction
, OnFocusChange
, OnItemClick
, OnItemLongClick
,OnItemSelected
, OnPageChange
, OnTextChanged
, OnTouch
, OnCheckedChanged
.
Eliminate resource lookups in your Java code by using resource annotations on fields:
class ExampleActivity extends Activity {
@BindString(R.string.title) String title;
@BindDrawable(R.drawable.graphic) Drawable graphic;
@BindColor(R.color.red) int red; // int or ColorStateList field
@BindDimen(R.dimen.spacer) Float spacer; // int (for pixel size) or float (for exact value) field
// ...
}
The following resource types are available: BindArray
, BindBitmap
, BindBool
,BindColor
,BindDimen
,BindDrawable
,BindInt
,BindString
.
There are two advanced features:
- Acting on Multiple Views In a List
- Type Inference for View Lookups
You can group multiple views into a List and perform actions on them as group:
// Group the views together
@BindViews({ R.id.first_name, R.id.middle_name, R.id.last_name })
List<EditText> nameViews;
The apply
method allows you to act on all the views in a list at once:
ButterKnife.apply(nameViews, DISABLE);
ButterKnife.apply(nameViews, ENABLED, false);
This requires writing Action
or Setter
interfaces allow specifying the action to perform:
static final ButterKnife.Action<View> DISABLE = new ButterKnife.Action<View>() {
@Override public void apply(View view, int index) {
view.setEnabled(false);
}
};
static final ButterKnife.Setter<View, Boolean> ENABLED = new ButterKnife.Setter<View, Boolean>() {
@Override public void set(View view, Boolean value, int index) {
view.setEnabled(value);
}
};
An Android property can also be used with the apply
method.
ButterKnife.apply(nameViews, View.ALPHA, 0.0f);
Included are findById
methods which simplify code for view lookups. It uses generics to infer the return type:
TextView firstName = ButterKnife.findById(view, R.id.first_name);
TextView lastName = ButterKnife.findById(view, R.id.last_name);
ImageView photo = ButterKnife.findById(view, R.id.photo);
Add a static import for ButterKnife.findById
and enjoy even more fun.
There are a few popular plugins for Android Studio that further simplify usage of Butterknife:
- butterknife-zelezny - Used to automatically generate the view lookup injection code.
Most common issue is seeing java.lang.NullPointerException: Attempt to invoke virtual method 'xxxxxx' on a null object reference
. This means Butterknife is not compiling the generated code properly or you have an issue with your layout file:
- Make sure that
ButterKnife.bind(this);
is located underneath bothsuper.onCreate
andsetContentView
within an activityonCreate
method. Note that you must include Butterknife binding in all files that reference Butterknife annotations (i.e fragments and adapters as well) - Use gradleplease to get the latest version of the library. Make sure both
implementation 'com.jakewharton:butterknife:X.X.X'
andannotationProcessor 'com.jakewharton:butterknife-compiler:X.X.X'
are present and have the exact same version. - Make sure the lines above are within the "app/build.gradle" (
build.gradle (Module: App)
) inside thedependencies
block. - Remove any mention of
apply plugin: 'com.neenbedankt.android-apt
as this is no longer needed, you should just need the two lines mentioned above. - Try to flush the caches and build files with
Build -> Clean
,Build -> Rebuild Project
,File -> Invalidate Caches and Restart
and then re-run the project. - Try restarting the emulator or device and then re-running the app
If you are using the Navigation Drawer from the latest version of the support library, you cannot use @BindView
on elements defined in the header layout because a RecyclerView is used instead of ListView in the newer versions, causing the header not be available immediately when the view is first created. To get a reference, you need to first get a reference to the header view and use the ButterKnife.findById
call once a reference to the header is obtained:
View headerView = navigationView.getHeaderView(0);
TextView textView = ButterKnife.findById(headerView, R.id.tvName);
In the past, ButterKnife was not supported when building your own Android libraries. However, in v8.2.0, additional support was included using a custom Gradle plugin. To use it, make sure your followed the previous steps to include the android-apt
and latest version of ButterKnife.
Next, add this Gradle plugin to the top of your library build.gradle
file:
buildscript {
dependencies {
classpath 'com.jakewharton:butterknife-gradle-plugin:8.2.1'
}
}
Below your com.android.library
plugin, you should apply this Gradle plugin:
apply plugin: 'com.android.library'
apply plugin: 'com.jakewharton.butterknife'
For an example of this setup, see this build.gradle file.
This Gradle plugin mostly creates a separate R2
class, which will be used inside your library projects instead:
@BindView(R2.id.title) TextView title;
@BindView(R2.id.subtitle) TextView subtitle;
Created by CodePath with much help from the community. Contributed content licensed under cc-wiki with attribution required. You are free to remix and reuse, as long as you attribute and use a similar license.
Finding these guides helpful?
We need help from the broader community to improve these guides, add new topics and keep the topics up-to-date. See our contribution guidelines here and our topic issues list for great ways to help out.
Check these same guides through our standalone viewer for a better browsing experience and an improved search. Follow us on twitter @codepath for access to more useful Android development resources.