Lesson Weekend

You might be getting tired of declaring variables for all of your Views at the top of your activity classes and then manually locating them by id's to set variables. Thankfully, there's a tool that can assist us with this! ButterKnife, after a little setup, will cut down on redundant code. ButterKnife is an injection library that allows for field and method binding for Android views.

ButterKnife Overview

Currently, whenever our back-end (or "business logic"; remember that term from Intro?) needs to interact with a portion of our user interface, we need to declare individual element of the user interface, then define them by manually locating them by id in the onCreate() method, like this:

public class MainActivity extends AppCompatActivity {
    private Button mFindRestaurantsButton;
    private EditText mLocationEditText;
    private TextView mAppNameTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        mLocationEditText = (EditText) findViewById(R.id.locationEditText);
        mFindRestaurantsButton = (Button) findViewById(R.id.findRestaurantsButton);
        mAppNameTextView = (TextView) findViewById(R.id.appNameTextView);
    ...

Using ButterKnife, we could instead simply include the ID of each View at the top of the file, where we previously declared member variables, and rely on ButterKnife to locate the views instead of manually finding them in `onCreate():

public class MainActivity extends AppCompatActivity {
    @Bind(R.id.findRestaurantsButton) Button mFindRestaurantsButton;
    @Bind(R.id.locationEditText) EditText mLocationEditText;
    @Bind(R.id.appNameTextView) TextView mAppNameTextView;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
    ...
        ButterKnife.bind(this);
...

With only three Views in our MainActivity, this may not look like a large reduction in code, but in the onCreate() method of the ButterKnife version, we only need to call the single line ButterKnife.bind(this); instead of calling findViewById() on every single element manually. As our application continues to grow in size and complexity, this will save us a lot of code.

Java Annotations

To use ButterKnife, we'll add special annotations specific to the ButterKnife library to our code. Java annotations are simply metadata that can be added to Java code to include additional information about a program. They're preceded by @. This symbol informs the compiler that what immediately follows it is an annotation. (The @Overrides we see in our existing code are annotations, too!)

When our project compiles, ButterKnife looks for the @Bind annotation seen above. When it finds one, it uses the additional information provided with it (the ID attribute, such as R.id.findRestaurantsButton) to find the corresponding view for us, so we don't have to explicitly do so in onCreate().

Implementing ButterKnife

To integrate ButterKnife into our project, we'll first need to add the library to our app's dependencies. We'll place the following line into our build.gradle(Module: app) file:

build.gradle(Module: app)
dependencies {
    ...
    compile 'com.jakewharton:butterknife:7.0.1'
}

Required Reading

Before we begin, visit ButterKnife's documentation to read more about how it works.

Refactoring MainActivity

Now, let's use this powerful tool to refactor our MainActivity. We should be able to take our code from this:

MainActivity.java
public class MainActivity extends AppCompatActivity {
    private Button mFindRestaurantsButton;
    private EditText mLocationEditText;
    private TextView mAppNameTextView;

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

        mLocationEditText = (EditText) findViewById(R.id.locationEditText);
        mFindRestaurantsButton = (Button) findViewById(R.id.findRestaurantsButton);
        mAppNameTextView = (TextView) findViewById(R.id.appNameTextView);

        Typeface ostrichFont = Typeface.createFromAsset(getAssets(), "fonts/ostrich-regular.ttf");
        mAppNameTextView.setTypeface(ostrichFont);

        mFindRestaurantsButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String location = mLocationEditText.getText().toString();
                Intent intent = new Intent(MainActivity.this, RestaurantsActivity.class);
                intent.putExtra("location", location);
                startActivity(intent);
            }
        });
    }
}

To this:

MainActivity.java
import butterknife.Bind;
import butterknife.ButterKnife;

public class MainActivity extends AppCompatActivity {
    @Bind(R.id.findRestaurantsButton) Button mFindRestaurantsButton;
    @Bind(R.id.locationEditText) EditText mLocationEditText;
    @Bind(R.id.appNameTextView) TextView mAppNameTextView;

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

        Typeface ostrichFont = Typeface.createFromAsset(getAssets(), "fonts/ostrich-regular.ttf");
        mAppNameTextView.setTypeface(ostrichFont);

        mFindRestaurantsButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String location = mLocationEditText.getText().toString();
                Intent intent = new Intent(MainActivity.this, RestaurantsActivity.class);
                intent.putExtra("location", location);
                startActivity(intent);
            }
        });
    }
}

Refactoring RestaurantsActivity

We'll implement ButterKnife into our RestaurantsActivity, too! We should be able to take our code from this:

RestaurantsActivity.java
public class RestaurantsActivity extends AppCompatActivity {
    private TextView mLocationTextView;
    private ListView mListView;
    private String[] restaurants = new String[] {"Sweet Hereafter", "Cricket", "Hawthorne Fish House", "Viking Soul Food",
            "Red Square", "Horse Brass", "Dick's Kitchen", "Taco Bell", "Me Kha Noodle Bar",
            "La Bonita Taqueria", "Smokehouse Tavern", "Pembiche", "Kay's Bar", "Gnarly Grey", "Slappy Cakes", "Mi Mero Mole" };

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

        mListView = (ListView) findViewById(R.id.listView);
        mLocationTextView = (TextView) findViewById(R.id.locationTextView);

      ...

To this:

RestaurantsActivity.java
import butterknife.Bind;
import butterknife.ButterKnife;

public class RestaurantsActivity extends AppCompatActivity {
    @Bind(R.id.locationTextView) TextView mLocationTextView;
    @Bind(R.id.listView) ListView mListView;

    private String[] restaurants = new String[] {"Sweet Hereafter", "Cricket", "Hawthorne Fish House", "Viking Soul Food",
            "Red Square", "Horse Brass", "Dick's Kitchen", "Taco Bell", "Me Kha Noodle Bar",
            "La Bonita Taqueria", "Smokehouse Tavern", "Pembiche", "Kay's Bar", "Gnarly Grey", "Slappy Cakes", "Mi Mero Mole" };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_restaurants);
        ButterKnife.bind(this);
  ...

From this point forward, we will use ButterKnife to easily bind our views, and keep our code brief and well-refactored.

Additional Resources


Example GitHub Repo for MyRestaurants

Terminology


  • Annotations: A form of metadata that can be added to Java code to include information about a program that's not part of the program itself. For ButterKnife specifically, we'll use the @Bind annotation.

  • ButterKnife: An injection library that allows for field and method binding for Android views. View documentation here.

Examples


Without ButterKnife

public class MainActivity extends AppCompatActivity {
    private Button mFindRestaurantsButton;
    private EditText mLocationEditText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        mLocationEditText = (EditText) findViewById(R.id.locationEditText);
        mFindRestaurantsButton = (Button) findViewById(R.id.findRestaurantsButton);
    ...

With ButterKnife:

public class MainActivity extends AppCompatActivity {
    @Bind(R.id.findRestaurantsButton) Button mFindRestaurantsButton;
    @Bind(R.id.locationEditText) EditText mLocationEditText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    ...
        ButterKnife.bind(this);
...

Example GitHub Repo for MyRestaurants

Additional Resources