preface

Can you write an article about ViewBinding?

First of all, I’d like to thank the reader for taking the time to take a closer look at view binding, and I plan to use it in my project. Of course, there are other readers of the content I have recorded, will be updated later when time is available. Without further ado, let’s start studying!

An overview of the

During our development, we needed to get the ViewId in the XML layout file to display the assignment, and we used to use findViewById, which resulted in a lot of template code. Until Jake Wharton open-source the Butter Knife framework to Bind ViewId. Nearly two years after Google’s support for Kotlin, we started using The Android Kotlin Extensions. Import the layout file in the file and reference viewId directly. No additional operations required, most convenient.

Google has now added ViewBinding to Android Studio 3.6 Canary 11 and later.

Note: to use the ViewBinding feature, AndroidStudio needs to be upgraded to at least 3.6.

Analysis of the

This article analyzes ViewBinding from the following aspects:

  • What problems can be solved by using it;
  • Use process;
  • Comparison with previous methods;
  • The principle;

1. What problems can it solve

As the name implies, ViewBinding means how to bind a view to your code. So the main problem is how to safely and elegantly refer from code to view controls in XML layout files. Until now, the dominant way to build user interfaces on Android has been to use LAYOUT files in XML format.

2. Use the process

  • Enable ViewBinding in the Gradle file for the Module that you want to use ViewBinding
Android {........................ viewBinding { enabled =true}........................ }Copy the code
  • If the developer does not want to generate a Binding class for a layout file during use, it can be added to the root view of the layout using the following properties:
< androidx constraintlayout. Widget. Constraintlayout..................... Tools: viewBindingIgnore = "true" >..................... </androidx.constraintlayout.widget.ConstraintLayout>Copy the code
  • Compile this Module to get the binding class corresponding to the XML layout file

When ViewBinding is enabled in gradle files, the compiler generates a bound class for each layout file under this module.

Suppose we have the following XML layout file

<? The XML version = "1.0" encoding = "utf-8"? > <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity" tools:viewBindingIgnore="true" > <ImageView android:id="@+id/img_show" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/ic_launcher_background" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent"/> <TextView android:id="@+id/tv_content" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>Copy the code

The class name is named as follows: XML layout file names remove underscores, capitalize underscores, and Binding. For example, IF I have a layout file activity_main.xml, the generated class file will be ActivityMainbinding.java.

The generated class files are located in the Module path: Build \generated\data_binding_base_class_source_out\debug\out\ package name \databinding.

As shown below:

  • Use this build class to reference the controls in the XML layout file

Invoke the generated class ActivityDescriptionBinding inflate () method to obtain the class instance object, through the getRoot () method can obtain the outermost layer of the View in the layout file, in this case is a ConstraintLayout. The Activity’s setContentView() method sets the content for the Activity. Any view with an ID in the layout file will generate a public final property in the generated class, for example:


<TextView
        android:id="@+id/tv_content". />Copy the code

The corresponding generated fields are:

 @NonNull
 public final TextView tvContent;
Copy the code

You can then access it directly using the object instance, as shown in the following code:

public class MainActivity extends AppCompatActivity {

    private ActivityMainBinding activityMainBinding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // Set the layout file
        activityMainBinding = ActivityMainBinding.inflate(LayoutInflater.from(this));
        setContentView(activityMainBinding.getRoot());

        // Set the text
        activityMainBinding.tvContent.setText("Either you stop growing or you keep moving forward.");
        // Set the imageactivityMainBinding.imgShow.setImageResource(R.drawable.img); }}Copy the code

3. Comparison with previous methods

There are currently findViewById, ButterKnife, and if you use Kotlin you can also use the Kotlin Android Extensions.

The aspects of these ways are as follows:

ViewBinding has several advantages over the above methods:

  • Type Safety: findViewById, ButterKnife both have Type conversion problems, such as accidentally assigning a TextView error value to a Button variable, will report an error, this error is very easy to occur, the key is that the error also occurs at runtime, not compile time!

    In ViewBinding, the attributes in the binding class are generated according to the XML Layout file, so the type is correct.

  • Null Safety: findViewById, ButterKnife and Kotlin Android Extensions both have Null unsafe issues. What does that mean? It doesn’t exist when we access that View. Why does this happen? For example, the wrong Id was accidentally used, or the view did not exist when accessed.

    I’m using the wrong Id and I’m sure you’ve all experienced that, but when you access the view, it doesn’t exist. For example, we use a set of XML Layout files in landscape and portrait respectively. If the landscape contains a view that is not in portrait, NullPointer will appear when the screen rotates from landscape to portrait.

    In ViewBinding, the attributes in the binding class are generated from the XML Layout file, so the Id is correct. And it marks @nullable for views that only exist in a layout file in one configuration, as in this example:

@NonNull
 public final TextView tvContent;
Copy the code

In addition, the generated classes are nice enough to give you detailed comments. All of this is to remind programmers to pay attention to the special treatment of this view, which is Null in some cases.

  • Simple and elegant: The template code bound to view is automatically generated in other classes, making the Controlor class (Activity, Fragment) clearer.

Principle 4.

From the above analysis, I guess you have a pretty good idea of how it works. Google has added a new function to the Gradle plugin. When a module has ViewBinding enabled, it will scan the layout file of the module and generate the corresponding binding class. The familiar findViewById operations are in this auto-generated class, as shown below:


public final class ActivityMainBinding implements ViewBinding {
  @NonNull
  private final ConstraintLayout rootView;

  @NonNull
  public final ImageView imgShow;

  @NonNull
  public final TextView tvContent;

  private ActivityMainBinding(@NonNull ConstraintLayout rootView, @NonNull ImageView imgShow, @NonNull TextView tvContent) {
    this.rootView = rootView;
    this.imgShow = imgShow;
    this.tvContent = tvContent;
  }

  @Override
  @NonNull
  public ConstraintLayout getRoot(a) {
    return rootView;
  }

  @NonNull
  public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater) {
    return inflate(inflater, null.false);
  }

  @NonNull
  public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater,
      @Nullable ViewGroup parent, boolean attachToParent) {
    View root = inflater.inflate(R.layout.activity_main, parent, false);
    if (attachToParent) {
      parent.addView(root);
    }
    return bind(root);
  }

  @NonNull
  public static ActivityMainBinding bind(@NonNull View rootView) {
    // The body of this method is generated in a way you would not otherwise write.
    // This is done to optimize the compiled bytecode for size and performance.
    String missingId;
    missingId: {
      ImageView imgShow = rootView.findViewById(R.id.img_show);
      if (imgShow == null) {
        missingId = "imgShow";
        break missingId;
      }
      TextView tvContent = rootView.findViewById(R.id.tv_content);
      if (tvContent == null) {
        missingId = "tvContent";
        break missingId;
      }
      return new ActivityMainBinding((ConstraintLayout) rootView, imgShow, tvContent);
    }
    throw new NullPointerException("Missing required view with ID: ".concat(missingId)); }}Copy the code

The core code is the bind(@nonnull View rootView) method, and there are two overloaded methods inflate() that we normally use to get instances of the Binding class. These methods are public static. The bind(@nonnull View rootView) method should allow for delayed binding, but it should be used only rarely.

conclusion

Currently, ViewBinding features are not perfect, such as the inability to reference a view when an inClude tag is used in XML. But overall it’s pretty good. It’s much more convenient than findViewById and Butter Knife. ViewBinding is used without type conversions and null pointer exceptions. Because it is all defined in the binding class, the developer can use it directly.


The following is the public account (LongXuanzhigu). Our articles will be synchronized to this account to facilitate exchange and learning of Android knowledge. Welcome to follow: