2. Migrating to Butterknife view binding. [PR|TAG]

It’s time to remove a lot of the boilerplate view binding code we’ve created in our fragments; MainFragment , Example1Fragment , Example2AFragment , Example2BFragment , Example3ParentFragment , and Example3ChildFragment .

First, update the BaseFragment with some Butterknife code to setup the rest of our fragments with view binding capabilities.

We simply add the above code to our BaseFragment and that’s it! Now the rest of our fragments have the ability to use the power of Butterknife.

The view binding occurs in onViewStateRestored with the call to Butterknife.bind(this, view) , which will bind the BaseFragment and its subclasses with views, listeners, and resources (strings, colors, drawables, etc) obtained from the view provided by getView() . The Butterknife.bind method returns a reference to an Unbinder object, which we then use to unbind() our views in onDestroyView() . Unbinding the views sets the view references to null. This is useful in preventing leaking views in Fragments since fragments have different view lifecycle than activities.

Note: For a full guide on Butterknife, read the user guide.

Some fragments may not provide a UI, which is why we check if the view is null before we perform the binding.

Question: Why perform the binding in onViewStateRestored ? We bind the views in onViewStateRestored instead of in onCreateView or onViewCreated so that view state changed listeners are not invoked automatically without user interaction. If we bind before this method (e.g. onViewCreated ), then any OnCheckedChangeListener (and other such listeners) bound by Butterknife (or without Butterknife) will be invoked during fragment recreation (since Android itself saves and restores the views’ states), which may produce unwanted (or wanted) side effects. Take a look at this gist for a concrete example. The lifecycle order is as follows (same if added via xml or java or if retain instance is true): onAttach -> onCreateView -> onViewCreated -> onActivityCreated -> onViewStateRestored -> onResume . Note that the onCreate (and other lifecycle events) are omitted on purpose. The caveat to this approach is that views, listeners, and resources bound by Butterknife will be null until onViewStatedRestored . However, this will not pose any issues to the current state of the project as well as when we perform the refactor to MVP. Just be careful not to use any objects bound using Butterknife before onViewStateRestored . Another caveat to this approach is that the scroll position of a ListView or RecyclerView will not be retained automatically. The reason is that the list / recycler view adapter would have to be set in onViewStateRestored , which occurs after the restoration of the scroll position has been attempted by the OS. The scroll position would have to be manually saved and restored. There is nothing wrong with binding data into views at onViewStateRestored . Waiting until onViewStateRestored to bind data and perform other presentational logic has the advantage that the view’s restored state can be queried during the presentation. If you must bind your views before onViewStateRestored (such as in onCreateView , onViewCreated , and onActivityCreated ) for whatever reason, then do so. Just be aware of the side-effects (which you may even use on purpose, by design). Note: Fragments that return a null View in onCreateView results in onViewCreated and onViewStateRestored not being called. This means that Butterknife.bind will not get called, which is completely fine because there is no View to bind. Weirdly enough though, onDestroyView still gets called in this case. Note: The onViewStateRestored(Bundle) lifecycle method is only available beginning with API level 17. Supporting API levels below 17 down to 14 requires the use of AppCompatActivity , support Fragment , and dagger.android.support APIs. Take a look at this [PR] for the migration guide to using support APIs. The latest support API setup is available in the [master-support] branch.

Next, let’s take a look at the changes for MainFragment .

That is a lot of deleted code! The MainFragment no longer implements View.OnClickListener . Setting the click listeners for the 3 buttons in onViewCreated have been deleted. The ugly onClick method with the big switch statement has also been deleted. The OnClickListener of the 3 buttons ( example_1 , example_2 , and example_3 ) are now set using Butterknife’s @OnClick method annotation.

Note: Just like Dagger 2, class member variables or methods that are annotated with Butterknife annotations cannot be private.

Finally, let’s take a look at the changes for Example1Fragment . The changes for the other example fragments are exactly the same as that of the changes in Example1Fragment and thus are excluded from this article for brevity. You may review the rest of the changes in the [PR].