Android architecture component -- ViewBinding learning notes

Android architecture components – ViewBinding learning notes

1. Problems solved

The purpose of ViewBinding is to reduce the use of template code findViewById(int), improve development efficiency, and simplify the code of Activity and Fragment.

2. Using ViewBinding

The use of ViewBinding requires Android Studio version 3.6 or above, and it needs to be installed in the build.gradle Open manually in the file as follows:

android {
    ...

    buildFeatures {
        viewBinding = true
    }
}

After that, Android Studio will automatically generate the corresponding Java class for the layout file under the layout folder. The naming rule is to name the big hump of the file name and add Binding after it.

Like activity_main.xml The layout file is as follows:

<?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">

    <TextView
        android:id="@+id/timestampText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="20sp"
        android:textColor="@color/colorPrimary"
        android:gravity="center"
        android:padding="20dp"
        tools:text="Hello world"
        app:layout_constraintTop_toTopOf="parent"/>

    <Button
        android:id="@+id/timestampButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@id/timestampText"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        android:text="Get timestamp"/>

    <FrameLayout
        android:id="@+id/fragmentContainer"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_constraintTop_toBottomOf="@id/timestampButton"
        app:layout_constraintBottom_toBottomOf="parent"
        android:layout_marginTop="20dp"/>

</androidx.constraintlayout.widget.ConstraintLayout>

Will generate ActivityMainBinding.java Documents. Then in MainActivity, get an activitymainbinding instance through the static method inflate (layoutinflate) of activitymainbinding, and then directly access each control through the instance.

MainActivity.java As follows:

public class MainActivity extends AppCompatActivity {

    private ActivityMainBinding mBinding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
		
		// Get the instance of the 'Binding' class through the 'inflate (layoutinflate)' method, and get the root layout through the 'getRoot()' method
        mBinding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(mBinding.getRoot());

		// Access controls by accessing fields
        mBinding.timestampButton.setOnClickListener(v -> {
            mBinding.timestampText.setText(String.valueOf(System.currentTimeMillis()));
        });

        getSupportFragmentManager().beginTransaction()
                .replace(mBinding.fragmentContainer.getId(), new TestFragment())
                .commit();
    }
}

If it is in Fragment, it can be used as follows:

public class TestFragment extends Fragment {

    private FragmentTestBinding mBinding;

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
		
		// Use the 'inflate (layoutinflate, ViewGroup, Boolean)' method to get the 'Binding' instance. The subsequent usage is the same.
        mBinding = FragmentTestBinding.inflate(inflater, container, false);

        mBinding.include.prettyTimeButton.setOnClickListener(v -> {
            SimpleDateFormat sdf = new SimpleDateFormat("yyy-MM-dd HH:mm:ss", Locale.CHINA);
            String prettyText = sdf.format(new Date());
            mBinding.include.prettyTimeText.setText(prettyText);
        });

        return mBinding.getRoot();
    }

    @Override
    public void onDestroyView() {
        mBinding = null;
        super.onDestroyView();
    }
}

If you use < include / > you need to specify an id for it to ensure that ViewBinding can generate corresponding fields for it

If it is used in RecyclerView, use the bind(View) method to obtain the Binding instance in the ViewHolder constructor, and directly access the view through the instance in onCreateViewHolder, as follows:

public class TestRecyclerViewAdapter extends RecyclerView.Adapter<TestRecyclerViewAdapter.ViewHolder> {

    private List<RvBean> mList;

    public TestRecyclerViewAdapter(List<RvBean> list) {
        mList = list;
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.rv_item_test, parent, false);
        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        final RvBean item = mList.get(position);
        holder.mBinding.content.setText(item.content);
        holder.mBinding.title.setText(item.title);

        holder.mBinding.avatar.setImageResource(item.imageId);
    }

    @Override
    public int getItemCount() {
        return mList.size();
    }

    static class ViewHolder extends RecyclerView.ViewHolder{

        RvItemTestBinding mBinding;

        public ViewHolder(@NonNull View itemView) {
            super(itemView);
            mBinding = RvItemTestBinding.bind(itemView);
        }
    }

}

3. Principle analysis

The generated Binding file is as follows:

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

  @NonNull
  public final FrameLayout fragmentContainer;

  @NonNull
  public final Button timestampButton;

  @NonNull
  public final TextView timestampText;

  private ActivityMainBinding(@NonNull ConstraintLayout rootView,
      @NonNull FrameLayout fragmentContainer, @NonNull Button timestampButton,
      @NonNull TextView timestampText) {
    this.rootView = rootView;
    this.fragmentContainer = fragmentContainer;
    this.timestampButton = timestampButton;
    this.timestampText = timestampText;
  }

  @Override
  @NonNull
  public ConstraintLayout getRoot() {
    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.
    int id;
    missingId: {
      id = R.id.fragmentContainer;
      FrameLayout fragmentContainer = rootView.findViewById(id);
      if (fragmentContainer == null) {
        break missingId;
      }

      id = R.id.timestampButton;
      Button timestampButton = rootView.findViewById(id);
      if (timestampButton == null) {
        break missingId;
      }

      id = R.id.timestampText;
      TextView timestampText = rootView.findViewById(id);
      if (timestampText == null) {
        break missingId;
      }

      return new ActivityMainBinding((ConstraintLayout) rootView, fragmentContainer,
          timestampButton, timestampText);
    }
    String missingId = rootView.getResources().getResourceName(id);
    throw new NullPointerException("Missing required view with ID: ".concat(missingId));
  }
}

To access the root layout, ViewBinding provides the getRoot() method. At the same time, each control with id in the layout will generate a corresponding field for the caller to access.

The core method is the bind(View) method, which returns a Binding object. All the controls with id in the layout are constructed and set as fields through findViewById(int) method. Other methods, inflate (layoutinflate, ViewGroup, Boolean) and inflate (layoutinflate), are the encapsulation of the method.

4. Advantages and disadvantages

advantage:

  • Simplify the template code, get rid of findViewById(int), and greatly reduce the template code in Activity, Fragment and RecyclerView. When there are more controls in a page, the effect is obvious.
  • There is no null pointer problem. If findViewById(int) is used in SecondActivity to refer to the control id of MainActivity, it will be difficult to find out when it is not running. View Binding effectively solves this problem.
  • Type safety, there will be no problem of forced conversion of control type.

Disadvantages:

  • New classes are generated at compile time, resulting in an increase in compile time.
  • As the number of classes increases, the package size will increase when the layout is large.

Tags: Android Fragment Java xml

Posted on Sun, 14 Jun 2020 04:40:48 -0400 by Wes1890