ViewBinding and Databinding

What is a ViewBinding

Viewbinding is a feature of Android Jetpack that makes it easier to write code that interacts with views. After viewBinding is enabled in a module, a binding class is generated for each XML layout file in that module. Instances of the binding class contain direct references to all views that have ids in the corresponding layout.

usage

How to configure Viewbinding

Do the following configuration in build.gradle for each module

 android {
         ...
         viewBinding {
             enabled = true}}Copy the code

When view binding is enabled for a module, a binding class is generated for each XML layout file contained in that module. Each binding class contains references to the root view as well as all views with ids. The system generates the name of the Binding class by converting the name of the XML file to camel case and adding the word “Binding” to the end.

Viewbinding is used in an Activity

Example: Suppose you have a layout called activity_main.xml

 <LinearLayout 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"
     android:background="@color/white"
     tools:context=".MainActivity">
 ​
     <Button
         android:text="Here's the button."
         android:id="@+id/test_view_binding"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"/>
 ​
 </LinearLayout>
Copy the code

In an Activity, viewBinding is used as follows:

 class MainActivity : Activity() {
 ​
     // Declare variables first
     private lateinit var binding: ActivityMainBinding
     
     override fun onCreate(savedInstanceState: Bundle?). {
         super.onCreate(savedInstanceState)
         
         // Load the layout with a generated binding
         binding = ActivityMainBinding.inflate(layoutInflater)
         
         // Call getRoot() of the Binding class to get an instance of the root element in activity_main.xml
         val view = binding.root
         
         // Pass the root view to setContentView() to make it the active view on the screen
         setContentView(view)
         
         // Use the Binding instance to get the control
         binding.testViewBinding.text = "button"}}Copy the code

Viewbinding is used in fragments

Using a viewBinding on the Fragment, here is an example from the website

     private var _binding: ResultProfileBinding? = null
     // This property is only valid between onCreateView and
     // onDestroyView.
     private val binding get() = _binding!!
 ​
     override fun onCreateView(
         inflater: LayoutInflater,
         container: ViewGroup? , savedInstanceState:Bundle?).: View? {
         _binding = ResultProfileBinding.inflate(inflater, container, false)
         val view = binding.root
         return view
     }
 ​
     override fun onDestroyView(a) {
         super.onDestroyView()
         _binding = null
     }
     
Copy the code

And here’s why it’s a little bit weird to implement. First of all, kotlin is null safe, so using _binding directly is not possible. Why not declare the LateInit variable as in activity? This binding variable is only available in onCreateView and onDestroyView. Because our fragment life cycle is different from that of an activity, a fragment can extend beyond the life of its view and can cause a memory leak if it is not left empty.

Viewbinding contains the use of layouts in include tags

title_bar.xml

 
      
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:orientation="vertical" android:layout_width="match_parent"
     android:layout_height="match_parent">
     <Button
         android:text="include"
         android:id="@+id/test_include"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"/>
 ​
 </LinearLayout>
Copy the code

activity_main.xml

 
      
 <LinearLayout 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"
     android:background="@color/white"
     tools:context=".MainActivity">
     
     <! Add id to include tag -->
     <include
         android:id="@+id/title_bar"
         layout="@layout/title_bar"/>.</LinearLayout>
Copy the code

Use a layout that includes include tags in your activity

 class MainActivity : Activity() {
     // Declare variables first
     private lateinit var binding: ActivityMainBinding
 ​
     override fun onCreate(savedInstanceState: Bundle?).{...// Use the id of the include tag directly, then reference the id of the include layout according to the ID of the include
         binding.titleBar.testInclude.text = "hello"}}Copy the code

Viewbinding is used in the layout of labels that contain merge

The first step is to remove the id from the include tag, otherwise an error will be reported directly.

 <merge xmlns:android="http://schemas.android.com/apk/res/android">
 ​
     <Button
         android:text="include"
         android:id="@+id/test_include"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"/>
 ​
 </merge>
Copy the code

Next comes the use in activities

 private lateinit var binding: ActivityMainBinding
 // Declare variables
 private lateinit var titleBarBinding: TitleBarBinding
 override fun onCreate(savedInstanceState: Bundle?). {
     super.onCreate(savedInstanceState)
     binding = ActivityMainBinding.inflate(layoutInflater)
 ​
     // Call TitleBarBind's bind function to associate title_bar. XML with our activity_main.xml
     titleBarBinding = TitleBarBinding.bind(binding.root)
     val view = binding.root
     setContentView(view)
     // Reference the control directly with the titlrBarBinding variable
     titleBarBinding.testInclude.text = "button"
 ​
 }
Copy the code

Viewbinding contains the use in Adapter

 class BindingAdapter(val mData:List<String>): RecyclerView.Adapter<BindingAdapter.MyHolder>() {
     //Myholder takes a RvItemBinding, recyclerView. ViewHolder takes a View, and returns a root from this binding
     inner class MyHolder(binding: RvItemBinding):RecyclerView.ViewHolder(binding.root){
         val textView = binding.textView
     }
 ​
     override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyHolder {
         // First call the RvItemBinding inflate function to load the rv_item.xml layout
         val binding = RvItemBinding.inflate(LayoutInflater.from(parent.context),parent,false)
         return MyHolder(binding)
     }
 ​
     override fun getItemCount(a) = mData.size
     
     override fun onBindViewHolder(holder: MyHolder, position: Int) {
         // Use its own member variables directly through the holder
         holder.textView.text = mData[position]
     }
 }
Copy the code

What can we benefit from using viewBinding

The benefits of using viewBinding over traditional findViewById

1. Type safety: Don’t worry about casting errors

2. Easy to write, do not have to write a lot of declaration code, making the code inside the Activity more clean

Using viewBinding has advantages over synthetic

The Synthetic algorithm is implemented by a plug-in provided by Android. This can be referenced by adding the apply plugin: ‘kotlin-android-extensions’ to build.gradle.

 import androidx.appcompat.app.AppCompatActivity
 import android.os.Bundle
 import kotlinx.android.synthetic.main.activity_synthetic.*
 ​
 class SyntheticActivity : AppCompatActivity() {
     override fun onCreate(savedInstanceState: Bundle?). {
         super.onCreate(savedInstanceState)
         setContentView(R.layout.activity_synthetic)
         // Use synthetic_button directly to use the control
         synthetic_button.text = "hello"}}Copy the code

But this plugin is no longer recommended.

If this plugin is so useful, why is it not recommended by Google? Google doesn’t say that either. Let’s decompile the above code directly into Java code. You get the following code

 package com.eebbk.mvvmlearn;
 ​
 import android.os.Bundle;
 import android.view.View;
 import android.widget.Button;
 import androidx.appcompat.app.AppCompatActivity;
 import com.eebbk.mvvmlearn.R.id;
 import java.util.HashMap;
 import kotlin.Metadata;
 import kotlin.jvm.internal.Intrinsics;
 import org.jetbrains.annotations.Nullable;
 ​
 public final class SyntheticActivity extends AppCompatActivity {
     // Add a member variable
    private HashMap _$_findViewCache;
 ​
    protected void onCreate(@Nullable Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       this.setContentView(1300109);
       Button var10000 = (Button)this._$_findCachedViewById(id.synthetic_button);
       Intrinsics.checkExpressionValueIsNotNull(var10000, "synthetic_button");
       var10000.setText((CharSequence)"hello");
    }
 ​
     // Avoid duplicates with user-declared functions by naming a strange function and finding our view through this function
    public View _$_findCachedViewById(int var1) {
       if (this._$_findViewCache == null) {
          this._$_findViewCache = new HashMap();
       }
 ​
       View var2 = (View)this._$_findViewCache.get(var1);
       if (var2 == null) {
          var2 = this.findViewById(var1);
          this._$_findViewCache.put(var1, var2);
       }
 ​
       return var2;
    }
 ​
    public void _$_clearFindViewByIdCache() {
       if (this._$_findViewCache ! =null) {
          this._$_findViewCache.clear(); }}}Copy the code

You can see that we’re adding a new member variable to help us implement findViewById. This virtually increases our memory overhead. That’s one of the things, and one of the things is that it increases the instability of our program. Colleagues who have used this control will know.

He by introducing import kotlinx. Android. Synthetic. Main. Activity_synthetic. * to directly use control id use control. The problem is that if you accidentally introduce other layouts and use controls for other layouts, the error will not be detected at compile time. Is a runtime error. This runtime error makes our program unstable. Especially once the project is complicated, there are many controls with the same name, which will increase the instability of our program.

dataBinding

What is the databinding

Data binding: A data binding library is a support library that allows you to bind interface components in a layout to data sources in an application using declarative personalities rather than programmatically.

We can bind the value of a property of a control in a layout file to a value in our program.

usage

How do I configure databinding

To use databinding in your project, you need to do the following configuration in the build.gradle module

 android {
         ...
         dataBinding {
             enabled = true}}Copy the code

introductory

First the layout file is modified to make certain changes. The layout file has layout as the root tag. Where the data tag is our data element, followed by our view element.

 
      
 <layout xmlns:android="http://schemas.android.com/apk/res/android">
     <! -- Data elements -->
     <data>
         <variable
             name="user"
             type="com.eebbk.mvvmlearn.bean.User" />
     </data>
 ​
     <! -- View element -->
     <LinearLayout
         android:layout_width="match_parent"
         android:layout_height="match_parent">
         <TextView
             android:id="@+id/user_name"
             android:text="@{user.userName}"
             android:layout_width="200px"
             android:layout_height="70px"/>
 ​
     </LinearLayout>
 </layout>
Copy the code

You can use shortcuts to quickly create a Databinding layout. Use Alt+Enter in the layout file, then pop up the following window, and click Convert to Data Binding Layout to quickly Convert.

 data class User(var userName:String = "") {}Copy the code
 class DataActivity : AppCompatActivity() {
 ​
     lateinit var dataBindingActivity:ActivityDataBinding
     override fun onCreate(savedInstanceState: Bundle?). {
         super.onCreate(savedInstanceState)
         // Bind the layout to the activity using DataBindingUtil
         dataBindingActivity = DataBindingUtil.setContentView(this,R.layout.activity_data)
         val user = User("hello")
         // Assign values to the data elements in the layout file
         dataBindingActivity.user = user
         //user.userName = "hello world"}}Copy the code

So that’s the basic use of databinding. If I change the userName property of the user variable, does that change the property value of the control? The answer is: no.

So what do we do if we want the property value of the control to change? Let’s talk about one-way data binding.

One-way data binding

BaseObservable

You want the UI to refresh immediately after data changes, so you need an Observable to do this. Let’s walk through this one-way data binding step by step with an example.

 // Our entity class extends from BaseObservable
 class User():BaseObservable() {
     @get:Bindable
     var userName:String = ""
         set(value)  {
             field = value
             //BR is a class generated at compile time. It has a similar function to R.Java. NotifyPropertyChanged updates the view associated with this property. This step must be carried out.
             notifyPropertyChanged(BR.userName)
         }
 }
Copy the code

BaseObservable provides two methods, one notifyPropertyChanged(int fieldId) and the other notifyChange()

NotifyPropertyChanged (int fieldId) Refreshes one property of our entity class. NotifyChange () refreshes all properties of our entity class.

 
      
 <layout xmlns:android="http://schemas.android.com/apk/res/android">
     <data>
         <variable
             name="user"
             type="com.eebbk.mvvmlearn.bean.User" />
     </data>
 ​
     <LinearLayout
         android:layout_width="match_parent"
         android:layout_height="match_parent">
         <TextView
             android:id="@+id/user_name"
             android:text="@{user.userName}"
             android:layout_width="200px"
             android:layout_height="70px"/>
         <Button
             android:text="Change your name"
             android:id="@+id/change_name"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"/>
 ​
     </LinearLayout>
 </layout>
Copy the code
 class DataActivity : AppCompatActivity() {
 ​
     lateinit var dataBindingActivity:ActivityDataBinding
     override fun onCreate(savedInstanceState: Bundle?). {
         super.onCreate(savedInstanceState)
         dataBindingActivity = DataBindingUtil.setContentView(this,R.layout.activity_data)
         val user = User()
         user.userName = "hello"
         dataBindingActivity.user = user
         dataBindingActivity.changeName.setOnClickListener{
             // By resetting the userName of the user, the UI will refresh in time.
             user.userName = "hello world"}}}Copy the code
ObservableField

The above is how to use a BaseObservable, which is quite complicated. So we also have an ObservableField class. This class is also based on a BaseObservable wrapper, but its usage is simpler.

 class User2 {
     var userName:ObservableField<String> = ObservableField("")}Copy the code
. dataBindingActivity.changeName.setOnClickListener{// The set function is used to change the value of userName. User2.username = ObservableField("") cannot update the UI because the ObservableField notifyChange() is executed only when the set function is executed.
     user2.userName.set(Hello world.)}Copy the code

Two-way binding

One-way binding is when the data changes and the bound UI view is refreshed. Does our data change when the data in the UI changes? The answer must be yes. So how do we implement this data change in our view, and our application’s data change as well? This is where bidirectional binding is needed. Android :text=”@{user.username}” Android :text=”@={user.username}” android:text=”@={user.username}” android:text=”@={user.username}” Of course, there will be LiveData data structures in JetPack that will make it easier to implement Databinding.

event

Event binding is setting variable binding to the callback interface. Commonly used for event binding are

  • The onClick,
  • OnLongClick,
  • AfterTextChanged,
  • OnTextChanged, etc.

Here is an example of a login

 
      
 <layout  xmlns:android="http://schemas.android.com/apk/res/android">
     <data>
         <variable
             name="userInfo"
             type="com.eebbk.mvvmlearn.bean.UserInfo" />
 ​
         <variable
             name="loginPresenter"
             type="com.eebbk.mvvmlearn.LoginPresenter" />
     </data>
     <LinearLayout
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:orientation="vertical">
         <EditText
             android:afterTextChanged = "@{loginPresenter.setLoginName}"
             android:id="@+id/login_name"
             android:layout_width="match_parent"
             android:layout_height="100px"/>
         <EditText
             android:afterTextChanged = "@{loginPresenter.setLoginPassword}"
             android:id="@+id/login_password"
             android:layout_width="match_parent"
             android:layout_height="100px"/>
         <Button
             android:onClick="@{()->loginPresenter.login(userInfo)}"
             android:text="Login"
             android:id="@+id/login_button"
             android:layout_width="match_parent"
             android:layout_height="100px"/>
 ​
     </LinearLayout>
 </layout>
Copy the code
 class LoginPresenter(private val userInfo: UserInfo,
                      private val binding: ActivityLoginBinding) {
     // Handle the login callback, click login to callback here
     fun login(userInfo: UserInfo){
         Log.d("hch",userInfo.toString())
     }
 ​
     // Handle the callback, which is called when the user name changes
     fun setLoginName(loginName:Editable){
         userInfo.loginName = loginName.toString()
         binding.userInfo = userInfo
     }
 ​
     // Handle the callback, which is called when the password changes
     fun setLoginPassword(loginPassword:Editable){
         userInfo.loginPassword = loginPassword.toString()
         binding.userInfo = userInfo
     }
 }
Copy the code

This is the event binding, of course, this login example can also be implemented by bidirectional binding, and it is much easier to implement.

Reference: guolin.blog.csdn.net/article/det…