No More findViewById

One of the little-known features of developing an Android application with Android Studio is data binding. With it comes many excellent features that I’ll be covering in future articles, but the most basic thing you get is elimination of findViewById.

Isn’t this just a pain in the neck?

TextView hello = (TextView) findViewById(R.id.hello);

There are tools available whose main job is to eliminate this small bit of code, but now there is an official way with Android Studio 1.5 and higher.

First, you must edit your Application’s build.gradle file and add the following into the android block:

android {

dataBinding.enabled = true
}

The next thing is to change the layout file by making the outer tag <layout> instead of whatever ViewGroup you use:

<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context=".MainActivity"
>

<TextView
android:id="@+id/hello"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>

</RelativeLayout>
</layout>

The layout tag tells Android Studio that this layout should take the extra processing during compilation time to find all the interesting Views and note them for the next step. All layouts without that outer layout tag will not get the extra processing step, so you can sprinkle these into new projects wherever you like without changing anything in the rest of your application.

The next thing you have to do is to tell it to load your layout file differently at runtime. Because this works all the way back to the Eclaire release, there is no reliance on new framework changes to load these preprocessed layout files. Therefore, you do have to make a slight change to your loading procedure.

From an Activity, instead of:

setContentView(R.layout.hello_world);
TextView hello = (TextView) findViewById(R.id.hello);
hello.setText("Hello World"); // for example, but you'd use
// resources, right?

You load it like this:

HelloWorldBinding binding = 
DataBindingUtil.setContentView(this, R.layout.hello_world);
binding.hello.setText("Hello World"); // you should use resources!

Here you can see that a class, HelloWorldBinding was generated for the hello_world.xml layout file and the View with the ID “@+id/hello” was assigned to a final field hello that you can use. No casting, no findViewById.

It turns out that this mechanism for accessing views is not only much easier than findViewById, but can also be faster! The binding process makes a single pass on all Views in the layout to assign the views to the fields. When you run findViewById, the view hierarchy is walked each time to find it.

One thing you will see is that it camel-casifies your variable names (just like hello_world.xml becomes the class HelloWorldBinding), so if you gave it the ID “@+id/hello_text” then the field name would be helloText.

When you’re inflating your layouts for RecyclerView, ViewPager, or other things that aren’t setting the Activity contents, you’ll want to use the generated type-safe methods on the generated class. There are several versions that match the LayoutInflater, so use the one that is most appropriate for your use. For example:

HelloWorldBinding binding = HelloWorldBinding.inflate(
getLayoutInflater(), container, attachToContainer);

If you aren’t attaching the inflated View to the containing ViewGroup, you’ll have to get access to the inflated View hierarchy. You can do this from the getRoot() method of the binding:

linearLayout.addView(binding.getRoot());

Now, you may be wondering, what if I have a layout with different configurations with some differing Views? The layout preprocessing and runtime inflation stages take care of this for you by adding all View IDs to the generated class and just sets them to null if they aren’t in the inflated layout.

Pretty magical, huh? The best part about this is that there is no reflection or any other high-cost techniques used at runtime. It is very easy to sprinkle this into your current applications to make your life just a little bit easier and your layouts may load just a tiny bit faster.