Before DataBinding came along, we couldn’t find a perfect solution. We had to listen for data changes and then synchronize the changes to the UI, which we did over and over again. DataBinding is designed to solve this problem by simply binding the data to the UI element, and the UI changes when the data is updated, and vice versa, greatly saving our code.

1. What is DataBinding?

In simple terms, DataBinding can be understood as a tool that addresses the problem of bidirectional binding between views and data.

2. The advantage of the DataBinding

Two-way data binding

When the data changes, DataBinding automatically notifies the UI to refresh the page, eliminating the need to manually bind the latest data to the View. UI changes can also be synchronized to data.

Reduce template code

With DataBinding, you don’t have to write findViewById, setOnClickListener, etc. From there ButterKnife stood aside.

The release of the Activity/fragments

In the past, we used to compute data in an Activity, Fragment, or Presenter and then bind it to a View component, resulting in a bloated View layer. Now we can do this directly in an XML layout file. Activities, fragments make it more focused on core business.

Data binding null security

Binding data in XML is null-safe because DataBinding does automatic boxing and null-determination on DataBinding, which greatly reduces null-pointer problems associated with DataBinding.

3. The use of the DataBinding

The introduction of DataBinding

Simply add the following configuration to the build.gradle file that uses the DataBinding module.

// Each module that uses dataBinding should add the following configuration to build.gradle
 android {
  ...
  dataBinding {
      enabled = true}}Copy the code

XML layout

In the layout file, select the TAB for the root layout, hold Alt+ Enter, and click Convert to Databinding Layout to Convert to databinding Layout (see code snippet below)

After the transformation, the outermost layer of the layout becomes the Layout tag, which wraps the Data tag and regular layout elements. The data element is used to declare variables used in the layout and their types, as well as class references.

Can all attributes be bound using DataBinding?

Of course not! If a property XXX has a setXxx method in that class, we can use DataBinding to bind it.

For example android:layout_width, Android_layout_height cannot use DataBinding to bind data.

Android :paddingLeft and Android :textSize are both ok.


      
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    <data>// We can use variable to define multiple variables, which require external assignment<variable
            name="user"
            type="org.devio.as.main.User" />// Use import to import the required classes<import type="android.view.View"/>
        <import type="org.devio.as.main.UserManager"/>
    </data>
    
    <androidx.constraintlayout.widget.ConstraintLayout>
        <TextView
            android:id="@+id/tvName"
            android:layout_width="200dp"// Cannot be useddataBindingDynamic bindingandroid:text="@{user.name}"// One-way binding, data changes automatically refreshUI
            android:textSize="@{@dimen/16sp}"// Resource referencesandroid:text="@{user.name+@string/suffix}"// Concatenation of strings requires referencing resourcesandroid:text="@{UserManager.getUserName()}"To call a static method, the class must be imported firstandroid:onClick="@{()-> UserManager.login()}"// event binding />

      <EditText// Two-way binding data changes are automatically updatedUI.UIChanges can also be automatically updateduserIn thenameMore data than one-way binding =// android:text="@{user.name}"Is equivalent totvName.text = User.name this associates the data with the View
            android:text="@={user.name}" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Copy the code

Bind delivery data source

Assign the User object in DataBinding

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // At this point, you can set the Activity's page layout using DataBindingUtil. An ActivityMainBinding object is returned. This is the implementation class that is automatically generated at compile time based on the data binding in the XML layout file.
    ActivityMainBinding  binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
    binding.lifecycleOwner = this
    binding.user = User('Joe') // Complete the data binding
    
    // If it is used in a list, it can be written as follows. The ActivityMainBinding is automatically generated from the Activity_main layout file
    val binding = ActivityMainBinding.inflate(layoutInflater, null.false)
    binding.user = User('Joe')}Copy the code

How do you automatically update views of data changes?

To automatically update views with data changes, we simply integrate the entity class User with the BaseObservable. When the User in the field is changed, just call. User notifyPropertyChanged can make the UI refresh.

public class User extends BaseObservable  {
    public String name;
    // If you want the UI to refresh automatically when the name field is changed, you need to write a get method to it and mark the Bindable annotation.
   
    @Bindable                   
    public String getName(a) {
        return name;
    }
    public void setName(String name) {
        this.name = name;
         // Finally call notifyPropertyChanged
         // The field has changed, notifying the UI to refresh the data
        notifyPropertyChanged(org.devio.as.main.BR.user);
    }
    
    // You can also use ObserverableBoolean... ObserverableInt to listen for changes in data
    ObserverableBoolean<Boolean> success = new   ObserverableBoolean<>();
}
Copy the code

DataBinding also supports arrays, lists, sets, and maps in layout files, all of which can be retrieved as List[index]. Because of the nature of XML, Angle bracket escape characters are required when declaring data types such as List.


      
<layout >
    <data>
        <import type="java.util.List" /> <import type="java.util.Map" />
        <import type="java.util.Set" /> <import type="android.util.SparseArray" />
        <variable
            name="array"
            type="String[]" />
        <variable
            name="list"
            type="List<String>" />            //List<String>The < and > characters need to be escaped<variable
            name="map"
            type="Map<String, String>" />     //Map<String>
        <variable
            name="set"
            type="Set<String>" />             //Set<String>
        <variable
            name="sparse"
            type="SparseArray<String>" />     //SparseArray<String>
        <variable
            name="index"
            type="int" />
        <variable
            name="key"
            type="String" />
    </data>

    <LinearLayout>
        <TextView
            android:text="@{array[1]}" />

        <TextView
            android:text="@{sparse[index]}" />

        <TextView
            android:text="@{list[index]}" />

        <TextView
            android:text="@{map[key]}" />

        <TextView
            android:text='@ {map [" jetpack for class "]}' />

        <TextView
            android:text='@{set.contains("xxx")?" For class jetpack ": the key} ' />
    </LinearLayout>
</layout>

Copy the code

DataBinding also supports rich syntax expressions for DataBinding in XML, paying for layout files using the following operators, expressions, and keywords:

  • Arithmetic operator: + – * / %;
  • String concatenation operator: +;
  • Logical operators: && | |.
  • Binary operators: & | ^;
  • Unary operator: + -! ~;
  • Displacement operator: >> >>> <<;
  • Comparison operators: == > < >= <= (needs to be escaped);
  • Check if it is an instanceof a class: instanceof;
  • Grouping operator :();
  • Literal operators – characters, strings, data, NULL;
  • Type conversion, method call;
  • Field access;
  • Array access: [];
  • Ternary operators:? :;
  • The following actions are not supported: This super New displays generic calls.

4. How does BataBinding extend View attributes

We know that in the past, to add several attributes to an ImageView, we had to write a custom ImageView that was parsed in the constructor. Let’s see how you can extend View properties using DataBinding.

public class HiImageView extends ImageView{

   // The BindingAdapter annotation is required and marked on the public static method.
    // The fields in value can be added to correspond to the method parameters.
   @BindingAdapter(value = {"image_url", "isCircle"})
    public static void setImageUrl(PPImageView view, String imageUrl, boolean isCircle) {
        view.setImageUrl(view, imageUrl, isCircle, 0);
    }
    //requireAll = false indicates whether this method is invoked only if the following three attributes are used together in XML
    // If false, this method can be called only if one attribute is used
    @BindingAdapter(value = {"image_url", "isCircle", "radius"}, requireAll = false)
    public static void setImageUrl(PPImageView view, String imageUrl, boolean isCircle, int radius) {... }}// Use the following in the layout file to implement the function of image rounded corners and resource Url binding
 <org.devio.as.main.HiImageView
            .......
            app:image_url ="@{user.avatar}"
            app:radius="@ {50}">
</org.devio.as.main.HiImageView>
Copy the code

5. How to Use DataBinding

  • Such as fragment_layout_my. XML layout, the compiler generates FragmentLayoutMyImpl. Java implementation class, we can search for this kind of debug follow up to solve the problem.

  • Messing with lists is not recommended because DataBinding DataBinding is delayed by one frame. If the width and height of an ItemView in a list need to be calculated before it can be displayed correctly, DataBinding is not recommended. Otherwise, you will see the list ItemView obviously split animation, which is a bad experience.

    Here can use dataBinding. ExecutePendingBindings () fast rendering layout to solve

  • The entity class and BaseObservable can solve the problem of two-way data binding in a friendly way.

6. What is ViewBinding?

Android Studio has been updated to 3.6 with a ViewBinding feature that looks similar to DataBinding. What’s the difference?

  • DataBinding allows you to bind a View to the data on the interface in both directions. ViewBinding does not allow you to bind data in XML. To do this you need to enable Gradle:

     viewBinding {
         enabled = true
     }
    Copy the code
  • If you want to implement two-way DataBinding, choose DataBinding;

  • ViewBinding saves us the findViewById process, but it takes less time than DataBinding at compile time;

  • If you already use Kotlin, ViewBinding is unnecessary.

7.DataBinding source code analysis

Confirm the loading of layout files

XML file location after XML separation

Layout written by the developer


      
<layout 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">
    <data><! This is used to define the data source.
        <variable
            name="user"
            type= "com.example.databindingdemo_20210117.User"/>
    </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <TextView
            android:textSize="50sp"
            android:id="@+id/tv1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.name}"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:textSize="50sp"
            android:id="@+id/tv2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.pwd}"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    </LinearLayout>

</layout>
Copy the code

Add a tag to the @{} View

app/build/imtermediates/data_binding_layout_info_type_merge/debug/activity_main-layout.xml


      
<Layout directory="layout" filePath="app\src\main\res\layout\activity_main.xml"
    isBindingData="true" isMerge="false" layout="activity_main"
    modulePackage="com.example.databindingdemo_20210117" rootNodeType="android.widget.LinearLayout">
    <Variables name="user" declared="true" type="com.example.databindingdemo_20210117.User">
        <location endLine="Seven" endOffset="62" startLine="5" startOffset="8" />
    </Variables>
    <Targets>
        <Target tag="layout/activity_main_0" view="LinearLayout">
            <Expressions />
            <location endLine="35" endOffset="18" startLine="9" startOffset="4" />
        </Target>
        <Target id="@+id/tv1" tag="binding_1" view="TextView">
            <Expressions>
                <Expression attribute="android:text" text="user.name">
                    <Location endLine="19" endOffset="38" startLine="19" startOffset="12" />
                    <TwoWay>false</TwoWay>
                    <ValueLocation endLine="19" endOffset="36" startLine="19" startOffset="28" />
                </Expression>
            </Expressions>
            <location endLine="23" endOffset="55" startLine="14" startOffset="8" />
        </Target>
        <Target id="@+id/tv2" tag="binding_2" view="TextView">
            <Expressions>
                <Expression attribute="android:text" text="user.pwd">
                    <Location endLine="30" endOffset="37" startLine="30" startOffset="12" />
                    <TwoWay>false</TwoWay>
                    <ValueLocation endLine="30" endOffset="35" startLine="30" startOffset="28" />
                </Expression>
            </Expressions>
            <location endLine="34" endOffset="55" startLine="25" startOffset="8" />
        </Target>
    </Targets>
</Layout>
Copy the code

app/build/imtermediates/incremental/mergeDebugResources/stripped.dir/layout/activity_main.xml


      

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical" android:tag="layout/activity_main_0" 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">

        <TextView
            android:textSize="50sp"
            android:id="@+id/tv1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:tag="binding_1"    
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:textSize="50sp"
            android:id="@+id/tv2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:tag="binding_2"   
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    </LinearLayout>
Copy the code

DataBindingUtil class

setContentView

DataBindingUtil.setContentView(Activity activitiy,int layoutId);
    ---> bindToAddedViews(bindingComponent,contentView,startChildren,layoutId)9;
        --->bind(compent,children,layoutId);  // Use databindingutil.inflate to go the same way
            //sMapper's implementation class is APT generated DataBinderMapperImpl class
            --->sMapper.getDataBinder(dindingComponent,root,layoutId);
Copy the code

DataBinderMapperImpl class generated by APT

In the app/build/generated/source/kapt/debug/com. XXX. XXX/DataBinderMapperImpl, he is sMapper getDataBinder (…) The implementation of the.

The Binding implementation for each layout can be obtained according to layoutId.

//DataBinderMapperImpl
@Overridepublic ViewDataBinding getDataBinder(DataBindingComponent component, View view, int layoutId) {  
    int localizedLayoutId = INTERNAL_LAYOUT_ID_LOOKUP.get(layoutId);  
    if(localizedLayoutId > 0) {    
        final Object tag = view.getTag();    
        if(tag == null) {      
            throw new RuntimeException("view must have a tag");    
        }   
        switch(localizedLayoutId) {      
            case  LAYOUT_ACTIVITYMAINTEST: {        
                if ("layout/activity_main_test_0".equals(tag)) {          
                    return new ActivityMainTestBindingImpl(component, view);        
                }        
                throw new IllegalArgumentException("The tag for activity_main_test is invalid. Received: "+ tag); }}}Copy the code

Java implementation of Binding for layout

In the app/build/generated/source/kapt/debug/com. XXX. XXX/databinding ActivityMainTestBindingImpl, It is a concrete implementation of the layout page activity_main_test.xml, but we call it with an ActivityMainTestBinding.

public class ActivityMainTestBindingImpl extends ActivityMainTestBinding  {
    
    
    public ActivityMainTestBindingImpl(@Nullable androidx.databinding.DataBindingComponent bindingComponent, @NonNull View root) {
        this(bindingComponent, root, mapBindings(bindingComponent, root, 3, sIncludes, sViewsWithIds));
    }
    
    
    private ActivityMainTestBindingImpl(androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) {
        super(bindingComponent, root, 1
            , (android.widget.TextView) bindings[1]
            , (android.widget.TextView) bindings[2]);this.mboundView0 = (android.widget.LinearLayout) bindings[0];
        this.mboundView0.setTag(null);
        this.tv1.setTag(null);
        this.tv2.setTag(null);
        setRootTag(root);
        // listenersinvalidateAll(); }}Copy the code

ViewDataBinding

// The XML file information is read here and stored in an array
protected static Object[] mapBindings(DataBindingComponent bindingComponent, View root,
            int numBindings, IncludedLayouts includes, SparseIntArray viewsWithIds) {
        Object[] bindings = new Object[numBindings];
        mapBindings(bindingComponent, root, bindings, includes, viewsWithIds, true);
        return bindings;
}
Copy the code

Source code analysis

The core principle is analyzed from setVariable(ID,value)

//ActivityMainTestBindingImpl
@Override
public boolean setVariable(int variableId, @Nullable Object variable)  {
        boolean variableSet = true;
        if (BR.user == variableId) {
            setUser((com.nearme.plugin.pay.activity.User) variable);
        }
        else {
            variableSet = false;
        }
            return variableSet;
}

public void setUser(@Nullable com.nearme.plugin.pay.activity.User User) {
        //1. Register listeners
        updateRegistration(0, User);  
        this.mUser = User;
        synchronized(this) {
            mDirtyFlags |= 0x1L;
        }
        //2. Invoke the listener callback
        notifyPropertyChanged(BR.user);
        super.requestRebind();
}
Copy the code

1. Register listeners

//ViewDataBinding
/ * * *@hide* /
protected boolean updateRegistration(int localFieldId, Observable observable) {
        CREATE_PROPERTY_LISTENER is a callback interface to update the viewer mode notification
        return updateRegistration(localFieldId, observable, CREATE_PROPERTY_LISTENER);
}

// Register listener
private boolean updateRegistration(int localFieldId, Object observable,
            CreateWeakListener listenerCreator) {
        if (observable == null) {
            return unregisterFrom(localFieldId);
        }
        WeakListener listener = mLocalFieldObservers[localFieldId];
        if (listener == null) {
            registerTo(localFieldId, observable, listenerCreator);
            return true;
        }
        if (listener.getTarget() == observable) {
            return false;
        }
        //1. Delete the listener
        // Call the unregister method of WeakPropertyListener and cancel the connection just established
        unregisterFrom(localFieldId);
        //2. Register listeners
        //listenerCreator is CREATE_PROPERTY_LISTENER
        registerTo(localFieldId, observable, listenerCreator);
        //3. Registration completed
        return true;
}

// Unlog the listener
protected boolean unregisterFrom(int localFieldId) {
        // Call the unregister method of WeakPropertyListener and cancel the connection just established
        WeakListener listener = mLocalFieldObservers[localFieldId];
        if(listener ! =null) {
            return listener.unregister();
        }
        return false;
}

// Register listener
protected void registerTo(int localFieldId, Object observable,
            CreateWeakListener listenerCreator) {
        if (observable == null) {
            return;
        }
        WeakListener listener = mLocalFieldObservers[localFieldId];
        if (listener == null) {
            Return new WeakPropertyListener(viewDataBinding, localFieldId).getListener()
            listener = listenerCreator.create(this, localFieldId);
            mLocalFieldObservers[localFieldId] = listener;
            if(mLifecycleOwner ! =null) { listener.setLifecycleOwner(mLifecycleOwner); }}/ / 688
        listener.setTarget(observable);
}


public void setTarget(T object) {
            unregister();
            mTarget = object;
            if(mTarget ! =null) {
                / / 1404mObservable.addListener(mTarget); }}private interface ObservableReference<T> {
        WeakListener<T> getListener(a);
        / / 1379
        / / - 1446 target of WeakPropertyListener addOnPropertyChangedCallback (this);
        / / - 40 addOnPropertyChangedCallback of observables (this);
        / / - 32 addOnPropertyChangedCallback BaseObservable
        //- 38 mCallbacks.add(callback); ViewDataBinding is stored in a List
        void addListener(T target);
        void removeListener(T target);
        void setLifecycleOwner(LifecycleOwner lifecycleOwner);
}
Copy the code

2. Invoke the listener callback

1. NotifyPropertyChanged
//BaseObservable
public void notifyPropertyChanged(int fieldId) {
        synchronized (this) {
            if (mCallbacks == null) {
                return; }}/ / 1.
        mCallbacks.notifyCallbacks(this, fieldId, null);
}
    
//PropertyChangeRegistry
public synchronized void notifyCallbacks(T sender, int arg, A arg2) {
        mNotificationLevel++;
        //2.notifiRecurse()->1.notifyRemainder()->notifyFirst64()-> notifyCallbacks()
                         //->2.mNotifier.onNotifyCallback()
        notifyRecurse(sender, arg, arg2);
        mNotificationLevel--;
        if (mNotificationLevel == 0) {
            if(mRemainderRemoved ! =null) {
                for (int i = mRemainderRemoved.length - 1; i >= 0; i--) {
                    final long removedBits = mRemainderRemoved[i];
                    if(removedBits ! =0) {
                        removeRemovedCallbacks((i + 1) * Long.SIZE, removedBits);
                        mRemainderRemoved[i] = 0; }}}if(mFirst64Removed ! =0) {
                removeRemovedCallbacks(0, mFirst64Removed);
                mFirst64Removed = 0; }}}Copy the code
2. Call the onPropertyChanged() method

The onPropertyChanged() method is called in the PropertyChangeRegistry class

public class PropertyChangeRegistry extends
        CallbackRegistry<Observable.OnPropertyChangedCallback.Observable.Void> {

    private static final CallbackRegistry.NotifierCallback<Observable.OnPropertyChangedCallback, Observable, Void> NOTIFIER_CALLBACK = new CallbackRegistry.NotifierCallback<Observable.OnPropertyChangedCallback, Observable, Void>() {
        @Override
        public void onNotifyCallback(Observable.OnPropertyChangedCallback callback, Observable sender,
                int arg, Void notUsed) {
                // Callback to WeakPropertyListener in ViewBinding
            callback.onPropertyChanged(sender, arg);  / / 30}}; }Copy the code
3. Call the onPropertyChanged method of ViewBinding

Callback to the onPropertyChanged method of a WeakPropertyListener in ViewBinding

//1.WeakPropertyListener
@Override
public void onPropertyChanged(Observable sender, int propertyId) {
            ViewDataBinding binder = mListener.getBinder();
            if (binder == null) {
                return;
            }
            Observable obj = mListener.getTarget();
            if(obj ! = sender) {return; 
            }
            / / 2.
            binder.handleFieldChange(mListener.mLocalFieldId, sender, propertyId);
}
        
//ViewDataBinding
private void handleFieldChange(int mLocalFieldId, Object object, int fieldId) {
        if (mInLiveDataRegisterObserver) {
            return;
        }
        / / 3. The concrete implementation in ActivityMainTestBindingImpl
        boolean result = onFieldChange(mLocalFieldId, object, fieldId);
        if (result) {
            //4. If onFieldChange() returns true, perform requestRebindrequestRebind(); }}//ActivityMainTestBindingImpl
@Override
protected boolean onFieldChange(int localFieldId, Object object, int fieldId) {
        switch (localFieldId) {
            case 0 :
                return onChangeUser((com.nearme.plugin.pay.activity.User) object, fieldId);
        }
        return false;
}
Copy the code
4. Update the UI
/ * * *@hide* /
protected void requestRebind(a) {    
    if(mContainingBinding ! =null) {        
        mContainingBinding.requestRebind();    
    } else {        
        final LifecycleOwner owner = this.mLifecycleOwner;        
        if(owner ! =null) {            
            Lifecycle.State state = owner.getLifecycle().getCurrentState();            
            if(! state.isAtLeast(Lifecycle.State.STARTED)) {return; }}synchronized (this) {            
            if (mPendingRebind) {                
                return;            
            }            
            mPendingRebind = true;        
        } 
        if (USE_CHOREOGRAPHER) { 
            mChoreographer.postFrameCallback(mFrameCallback);                 
        } else {  
            // Handle UI updates through the mRebindRunnable thread
            mUIThreadHandler.post(mRebindRunnable); 
            //executePendingBindings()->executeBindingsInternal->executeBindings->
           // executeBindings(); Find the ActivityMainBindingImpl class for the implementation}}}Copy the code
5. ActivityMainBindingImpl executeBindings

Implement specific UI update methods

3. Question: How is data binding done before setUser is called?

An implementation of the BaseObserable parent Observable

public abstract class ViewDataBinding extends BaseObservable implements ViewBinding {

//1. Static code blocks
static {
        if (VERSION.SDK_INT < VERSION_CODES.KITKAT) {
            ROOT_REATTACHED_LISTENER = null;
        } else {
            ROOT_REATTACHED_LISTENER = new OnAttachStateChangeListener() {
                @TargetApi(VERSION_CODES.KITKAT)
                @Override
                / / 2. The callback
                public void onViewAttachedToWindow(View v) {
                    // execute the pending bindings.
                    final ViewDataBinding binding = getBinding(v);
                    //3. Start a thread
                    //executePendingBindings()->executeBindingsInternal()->executeBindings()->
                    / / executeBindings ActivityMainBindingImpl () in
                    binding.mRebindRunnable.run();
                    v.removeOnAttachStateChangeListener(this);
                }

                @Override
                public void onViewDetachedFromWindow(View v) {}}; }}}Copy the code

conclusion

When using DataBinding, many students would mistake it for inconvenient debugging, but that was five years ago. Now DataBinding, like Room, has plenty of error messages at compile time. At run time, we can find the implementation class from the layout file and follow the breakpoints to troubleshoot problems.

DataBinding is often used in MVVM development patterns to reduce Activity/Fragment stress, but it is not a required part of MVVM.