Gradle dependencies
compile group: 'androidx.databinding', name: 'databinding-runtime', version: '8.8.0-alpha01'
- groupId: androidx.databinding
- artifactId: databinding-runtime
- version: 8.8.0-alpha01
Artifact androidx.databinding:databinding-runtime:8.8.0-alpha01 it located at Google repository (https://maven.google.com/)
Androidx class mapping:
androidx.databinding.ViewDataBinding android.databinding.ViewDataBinding
Overview
Base class for generated data binding classes. If possible, the generated binding should
be instantiated using one of its generated static bind or inflate methods. If the specific
binding is unknown, DataBindingUtil.bind(View) or
DataBindingUtil.inflate(LayoutInflater, int, ViewGroup, boolean) should be used.
Summary
Methods |
---|
public void | addOnRebindCallback(OnRebindCallback listener)
Add a listener to be called when reevaluating dirty fields. |
protected static ViewDataBinding | bind(java.lang.Object bindingComponent, View view, int layoutId)
|
protected void | ensureBindingComponentIsNotNull(java.lang.Class<java.lang.Object> oneExample)
|
protected abstract void | executeBindings()
|
protected static void | executeBindingsOn(ViewDataBinding other)
Calls executeBindingsInternal on the other ViewDataBinding |
public void | executePendingBindings()
Evaluates the pending bindings, updating any Views that have expressions bound to
modified variables. |
public static int | getBuildSdkInt()
|
protected static int | getColorFromResource(View view, int resourceId)
|
protected static ColorStateList | getColorStateListFromResource(View view, int resourceId)
|
protected static Drawable | getDrawableFromResource(View view, int resourceId)
|
protected static java.lang.Object | getFrom(java.util.Map<java.lang.Object, java.lang.Object> map, java.lang.Object key)
|
protected static boolean | getFromArray(boolean[] arr[], int index)
|
protected static byte | getFromArray(byte[] arr[], int index)
|
protected static char | getFromArray(char[] arr[], int index)
|
protected static double | getFromArray(double[] arr[], int index)
|
protected static float | getFromArray(float[] arr[], int index)
|
protected static int | getFromArray(int[] arr[], int index)
|
protected static long | getFromArray(long[] arr[], int index)
|
protected static short | getFromArray(short[] arr[], int index)
|
protected static java.lang.Object | getFromArray(java.lang.Object arr[], int index)
|
protected static java.lang.Object | getFromList(<any> list, int index)
|
protected static java.lang.Object | getFromList(<any> list, int index)
|
protected static java.lang.Object | getFromList(java.util.List<java.lang.Object> list, int index)
|
protected static boolean | getFromList(SparseBooleanArray list, int index)
|
protected static int | getFromList(SparseIntArray list, int index)
|
protected static long | getFromList(SparseLongArray list, int index)
|
public LifecycleOwner | getLifecycleOwner()
Returns the current LifecycleOwner assigned to the binding. |
protected java.lang.Object | getObservedField(int localFieldId)
|
public View | getRoot()
Returns the outermost View in the layout file associated with the Binding. |
protected void | handleFieldChange(int mLocalFieldId, java.lang.Object object, int fieldId)
|
public abstract boolean | hasPendingBindings()
Returns whether the UI needs to be refresh to represent the current data. |
protected static ViewDataBinding | inflateInternal(LayoutInflater inflater, int layoutId, ViewGroup parent, boolean attachToParent, java.lang.Object bindingComponent)
Pass through inflate method for generated code that receives bindingComponent as an object. |
public abstract void | invalidateAll()
Invalidates all binding expressions and requests a new rebind to refresh UI. |
protected static java.lang.Object | mapBindings(DataBindingComponent bindingComponent, View roots[], int numBindings, ViewDataBinding.IncludedLayouts includes, SparseIntArray viewsWithIds)
Walks the view hierarchy under roots and pulls out tagged Views, includes, and views with
IDs into an Object[] that is returned. |
protected static java.lang.Object | mapBindings(DataBindingComponent bindingComponent, View root, int numBindings, ViewDataBinding.IncludedLayouts includes, SparseIntArray viewsWithIds)
Walks the view hierarchy under root and pulls out tagged Views, includes, and views with
IDs into an Object[] that is returned. |
protected abstract boolean | onFieldChange(int localFieldId, java.lang.Object object, int fieldId)
Called when an observed object changes. |
protected static boolean | parse(java.lang.String str, boolean fallback)
|
protected static byte | parse(java.lang.String str, byte fallback)
|
protected static char | parse(java.lang.String str, char fallback)
|
protected static double | parse(java.lang.String str, double fallback)
|
protected static float | parse(java.lang.String str, float fallback)
|
protected static int | parse(java.lang.String str, int fallback)
|
protected static long | parse(java.lang.String str, long fallback)
|
protected static short | parse(java.lang.String str, short fallback)
|
protected void | registerTo(int localFieldId, java.lang.Object observable, androidx.databinding.CreateWeakListener listenerCreator)
|
public void | removeOnRebindCallback(OnRebindCallback listener)
Removes a listener that was added in ViewDataBinding.addOnRebindCallback(OnRebindCallback). |
protected void | requestRebind()
|
protected static boolean | safeUnbox(java.lang.Boolean boxed)
|
protected static byte | safeUnbox(java.lang.Byte boxed)
|
protected static char | safeUnbox(java.lang.Character boxed)
|
protected static double | safeUnbox(java.lang.Double boxed)
|
protected static float | safeUnbox(java.lang.Float boxed)
|
protected static int | safeUnbox(java.lang.Integer boxed)
|
protected static long | safeUnbox(java.lang.Long boxed)
|
protected static short | safeUnbox(java.lang.Short boxed)
|
protected static void | setBindingInverseListener(ViewDataBinding binder, InverseBindingListener oldListener, ViewDataBinding.PropertyChangedInverseListener listener)
|
protected void | setContainedBinding(ViewDataBinding included)
Used internally to set the containing binding for an included binding to this. |
public void | setLifecycleOwner(LifecycleOwner lifecycleOwner)
Sets the LifecycleOwner that should be used for observing changes of
LiveData in this binding. |
protected void | setRootTag(View view)
|
protected void | setRootTag(View views[])
|
protected static void | setTo(<any> list, int index, java.lang.Object value)
|
protected static void | setTo(<any> list, int index, java.lang.Object value)
|
protected static void | setTo(boolean[] arr[], int index, boolean value)
|
protected static void | setTo(byte[] arr[], int index, byte value)
|
protected static void | setTo(char[] arr[], int index, char value)
|
protected static void | setTo(double[] arr[], int index, double value)
|
protected static void | setTo(float[] arr[], int index, float value)
|
protected static void | setTo(int[] arr[], int index, int value)
|
protected static void | setTo(java.util.List<java.lang.Object> list, int index, java.lang.Object value)
|
protected static void | setTo(long[] arr[], int index, long value)
|
protected static void | setTo(java.util.Map<java.lang.Object, java.lang.Object> map, java.lang.Object key, java.lang.Object value)
|
protected static void | setTo(short[] arr[], int index, short value)
|
protected static void | setTo(SparseBooleanArray list, int index, boolean value)
|
protected static void | setTo(SparseIntArray list, int index, int value)
|
protected static void | setTo(SparseLongArray list, int index, long value)
|
protected static void | setTo(java.lang.Object arr[], int index, java.lang.Object value)
|
public abstract boolean | setVariable(int variableId, java.lang.Object value)
Set a value value in the Binding class. |
public void | unbind()
Removes binding listeners to expression variables. |
protected boolean | unregisterFrom(int localFieldId)
|
protected boolean | updateLiveDataRegistration(int localFieldId, LiveData<java.lang.Object> observable)
|
protected boolean | updateRegistration(int localFieldId, java.lang.Object observable, androidx.databinding.CreateWeakListener listenerCreator)
|
protected boolean | updateRegistration(int localFieldId, Observable observable)
|
protected boolean | updateRegistration(int localFieldId, ObservableList observable)
|
protected boolean | updateRegistration(int localFieldId, ObservableMap observable)
|
from BaseObservable | addOnPropertyChangedCallback, notifyChange, notifyPropertyChanged, removeOnPropertyChangedCallback |
from java.lang.Object | clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait |
Fields
public static final java.lang.String
BINDING_TAG_PREFIXPrefix for android:tag on Views with binding. The root View and include tags will not have
android:tag attributes and will use ids instead.
The DataBindingComponent used by this data binding. This is used for BindingAdapters
that are instance methods to retrieve the class instance that implements the
adapter.
protected boolean
mInStateFlowRegisterObserverWhen StateFlow first collects for chances, it notifies immediately that there was a
change. This flag identifies that we've just started observing StateFlow and we should ignore
the change notification.
Constructors
Needed for backwards binary compatibility.
b/122936785
protected
ViewDataBinding(java.lang.Object bindingComponent, View root, int localFieldCount)
Methods
protected void
setRootTag(View view)
protected void
setRootTag(View views[])
public static int
getBuildSdkInt()
Returns:
Returns the current build sdk
protected abstract boolean
onFieldChange(int localFieldId, java.lang.Object object, int fieldId)
Called when an observed object changes. Sets the appropriate dirty flag if applicable.
Parameters:
localFieldId: The index into mLocalFieldObservers that this Object resides in.
object: The object that has changed.
fieldId: The BR ID of the field being changed or _all if
no specific field is being notified.
Returns:
true if this change should cause a change to the UI.
public abstract boolean
setVariable(int variableId, java.lang.Object value)
Set a value value in the Binding class.
Typically, the developer will be able to call the subclass's set method directly. For
example, if there is a variable x
in the Binding, a setX
method
will be generated. However, there are times when the specific subclass of ViewDataBinding
is unknown, so the generated method cannot be discovered without reflection. The
setVariable call allows the values of variables to be set without reflection.
Parameters:
variableId: the BR id of the variable to be set. For example, if the variable is
x
, then variableId will be BR.x
.
value: The new value of the variable to be set.
Returns:
true
if the variable is declared or used in the binding or
false
otherwise.
Sets the LifecycleOwner that should be used for observing changes of
LiveData in this binding. If a LiveData is in one of the binding expressions
and no LifecycleOwner is set, the LiveData will not be observed and updates to it
will not be propagated to the UI.
When using Data Binding with Fragments, make sure to use Fragment.getViewLifecycleOwner().
Using the Fragment as the LifecycleOwner might cause memory leaks since the Fragment's
Lifecycle outlives the view Lifecycle.
When using Data Binding with Activities, you can use the Activity as the LifecycleOwner.
Parameters:
lifecycleOwner: The LifecycleOwner that should be used for observing changes of
LiveData in this binding.
Returns the current LifecycleOwner assigned to the binding.
Might be null if not set.
Returns:
The current LifecycleOwner used by the binding.
Add a listener to be called when reevaluating dirty fields. This also allows automatic
updates to be halted, but does not stop explicit calls to ViewDataBinding.executePendingBindings().
Parameters:
listener: The listener to add.
Removes a listener that was added in ViewDataBinding.addOnRebindCallback(OnRebindCallback).
Parameters:
listener: The listener to remove.
public void
executePendingBindings()
Evaluates the pending bindings, updating any Views that have expressions bound to
modified variables. This must be run on the UI thread.
Calls executeBindingsInternal on the other ViewDataBinding
protected abstract void
executeBindings()
public abstract void
invalidateAll()
Invalidates all binding expressions and requests a new rebind to refresh UI.
public abstract boolean
hasPendingBindings()
Returns whether the UI needs to be refresh to represent the current data.
Returns:
true if any field has changed and the binding should be evaluated.
Removes binding listeners to expression variables.
Returns the outermost View in the layout file associated with the Binding. If this
binding is for a merge layout file, this will return the first root in the merge tag.
Returns:
the outermost View in the layout file associated with the Binding.
protected void
handleFieldChange(int mLocalFieldId, java.lang.Object object, int fieldId)
protected boolean
unregisterFrom(int localFieldId)
protected void
requestRebind()
protected java.lang.Object
getObservedField(int localFieldId)
protected boolean
updateRegistration(int localFieldId, java.lang.Object observable, androidx.databinding.CreateWeakListener listenerCreator)
protected boolean
updateRegistration(int localFieldId,
Observable observable)
protected boolean
updateRegistration(int localFieldId,
ObservableList observable)
protected boolean
updateRegistration(int localFieldId,
ObservableMap observable)
protected boolean
updateLiveDataRegistration(int localFieldId,
LiveData<java.lang.Object> observable)
protected void
ensureBindingComponentIsNotNull(java.lang.Class<java.lang.Object> oneExample)
protected void
registerTo(int localFieldId, java.lang.Object observable, androidx.databinding.CreateWeakListener listenerCreator)
protected static
ViewDataBinding bind(java.lang.Object bindingComponent, View view, int layoutId)
Walks the view hierarchy under root and pulls out tagged Views, includes, and views with
IDs into an Object[] that is returned. This is used to walk the view hierarchy once to find
all bound and ID'd views.
Parameters:
bindingComponent: The binding component to use with this binding.
root: The root of the view hierarchy to walk.
numBindings: The total number of ID'd views, views with expressions, and includes
includes: The include layout information, indexed by their container's index.
viewsWithIds: Indexes of views that don't have tags, but have IDs.
Returns:
An array of size numBindings containing all Views in the hierarchy that have IDs
(with elements in viewsWithIds), are tagged containing expressions, or the bindings for
included layouts.
protected static boolean
parse(java.lang.String str, boolean fallback)
protected static byte
parse(java.lang.String str, byte fallback)
protected static short
parse(java.lang.String str, short fallback)
protected static int
parse(java.lang.String str, int fallback)
protected static long
parse(java.lang.String str, long fallback)
protected static float
parse(java.lang.String str, float fallback)
protected static double
parse(java.lang.String str, double fallback)
protected static char
parse(java.lang.String str, char fallback)
protected static int
getColorFromResource(View view, int resourceId)
protected static ColorStateList
getColorStateListFromResource(View view, int resourceId)
protected static Drawable
getDrawableFromResource(View view, int resourceId)
protected static java.lang.Object
getFromArray(java.lang.Object arr[], int index)
protected static void
setTo(java.lang.Object arr[], int index, java.lang.Object value)
protected static boolean
getFromArray(boolean[] arr[], int index)
protected static void
setTo(boolean[] arr[], int index, boolean value)
protected static byte
getFromArray(byte[] arr[], int index)
protected static void
setTo(byte[] arr[], int index, byte value)
protected static short
getFromArray(short[] arr[], int index)
protected static void
setTo(short[] arr[], int index, short value)
protected static char
getFromArray(char[] arr[], int index)
protected static void
setTo(char[] arr[], int index, char value)
protected static int
getFromArray(int[] arr[], int index)
protected static void
setTo(int[] arr[], int index, int value)
protected static long
getFromArray(long[] arr[], int index)
protected static void
setTo(long[] arr[], int index, long value)
protected static float
getFromArray(float[] arr[], int index)
protected static void
setTo(float[] arr[], int index, float value)
protected static double
getFromArray(double[] arr[], int index)
protected static void
setTo(double[] arr[], int index, double value)
protected static java.lang.Object
getFromList(java.util.List<java.lang.Object> list, int index)
protected static void
setTo(java.util.List<java.lang.Object> list, int index, java.lang.Object value)
protected static java.lang.Object
getFromList(<any> list, int index)
protected static void
setTo(<any> list, int index, java.lang.Object value)
protected static java.lang.Object
getFromList(<any> list, int index)
protected static void
setTo(<any> list, int index, java.lang.Object value)
protected static boolean
getFromList(SparseBooleanArray list, int index)
protected static void
setTo(SparseBooleanArray list, int index, boolean value)
protected static int
getFromList(SparseIntArray list, int index)
protected static void
setTo(SparseIntArray list, int index, int value)
protected static long
getFromList(SparseLongArray list, int index)
protected static void
setTo(SparseLongArray list, int index, long value)
protected static java.lang.Object
getFrom(java.util.Map<java.lang.Object, java.lang.Object> map, java.lang.Object key)
protected static void
setTo(java.util.Map<java.lang.Object, java.lang.Object> map, java.lang.Object key, java.lang.Object value)
protected static int
safeUnbox(java.lang.Integer boxed)
protected static long
safeUnbox(java.lang.Long boxed)
protected static short
safeUnbox(java.lang.Short boxed)
protected static byte
safeUnbox(java.lang.Byte boxed)
protected static char
safeUnbox(java.lang.Character boxed)
protected static double
safeUnbox(java.lang.Double boxed)
protected static float
safeUnbox(java.lang.Float boxed)
protected static boolean
safeUnbox(java.lang.Boolean boxed)
Used internally to set the containing binding for an included binding to this.
Walks the view hierarchy under roots and pulls out tagged Views, includes, and views with
IDs into an Object[] that is returned. This is used to walk the view hierarchy once to find
all bound and ID'd views.
Parameters:
bindingComponent: The binding component to use with this binding.
roots: The root Views of the view hierarchy to walk. This is used with merge tags.
numBindings: The total number of ID'd views, views with expressions, and includes
includes: The include layout information, indexed by their container's index.
viewsWithIds: Indexes of views that don't have tags, but have IDs.
Returns:
An array of size numBindings containing all Views in the hierarchy that have IDs
(with elements in viewsWithIds), are tagged containing expressions, or the bindings for
included layouts.
protected static
ViewDataBinding inflateInternal(LayoutInflater inflater, int layoutId, ViewGroup parent, boolean attachToParent, java.lang.Object bindingComponent)
Pass through inflate method for generated code that receives bindingComponent as an object.
Only called by the generated code.
See b/116541301 for details.
Source
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.databinding;
import android.annotation.TargetApi;
import android.util.Log;
import androidx.annotation.RestrictTo;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.Observer;
import androidx.lifecycle.OnLifecycleEvent;
import android.content.res.ColorStateList;
import androidx.databinding.CallbackRegistry.NotifierCallback;
import android.graphics.drawable.Drawable;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.os.Handler;
import android.os.Looper;
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.text.TextUtils;
import android.util.LongSparseArray;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import android.util.SparseLongArray;
import android.view.Choreographer;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnAttachStateChangeListener;
import android.view.ViewGroup;
import androidx.viewbinding.ViewBinding;
import androidx.databinding.library.R;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.List;
import java.util.Map;
/**
* Base class for generated data binding classes. If possible, the generated binding should
* be instantiated using one of its generated static bind or inflate methods. If the specific
* binding is unknown, {@link DataBindingUtil#bind(View)} or
* {@link DataBindingUtil#inflate(LayoutInflater, int, ViewGroup, boolean)} should be used.
*/
public abstract class ViewDataBinding extends BaseObservable implements ViewBinding {
/**
* Instead of directly accessing Build.VERSION.SDK_INT, generated code uses this value so that
* we can test API dependent behavior.
*/
static int SDK_INT = VERSION.SDK_INT;
private static final int REBIND = 1;
private static final int HALTED = 2;
private static final int REBOUND = 3;
/**
* Prefix for android:tag on Views with binding. The root View and include tags will not have
* android:tag attributes and will use ids instead.
*
* @hide
*/
public static final String BINDING_TAG_PREFIX = "binding_";
// The length of BINDING_TAG_PREFIX prevents calling length repeatedly.
private static final int BINDING_NUMBER_START = BINDING_TAG_PREFIX.length();
private static final boolean USE_CHOREOGRAPHER = SDK_INT >= 16;
/**
* Method object extracted out to attach a listener to a bound Observable object.
*/
private static final CreateWeakListener CREATE_PROPERTY_LISTENER = new CreateWeakListener() {
@Override
public WeakListener create(
ViewDataBinding viewDataBinding,
int localFieldId,
ReferenceQueue<ViewDataBinding> referenceQueue
) {
return new WeakPropertyListener(viewDataBinding, localFieldId, referenceQueue)
.getListener();
}
};
/**
* Method object extracted out to attach a listener to a bound ObservableList object.
*/
private static final CreateWeakListener CREATE_LIST_LISTENER = new CreateWeakListener() {
@Override
public WeakListener create(
ViewDataBinding viewDataBinding,
int localFieldId,
ReferenceQueue<ViewDataBinding> referenceQueue
) {
return new WeakListListener(viewDataBinding, localFieldId, referenceQueue)
.getListener();
}
};
/**
* Method object extracted out to attach a listener to a bound ObservableMap object.
*/
private static final CreateWeakListener CREATE_MAP_LISTENER = new CreateWeakListener() {
@Override
public WeakListener create(
ViewDataBinding viewDataBinding,
int localFieldId,
ReferenceQueue<ViewDataBinding> referenceQueue
) {
return new WeakMapListener(viewDataBinding, localFieldId, referenceQueue)
.getListener();
}
};
/**
* Method object extracted out to attach a listener to a bound LiveData object.
*/
private static final CreateWeakListener CREATE_LIVE_DATA_LISTENER = new CreateWeakListener() {
@Override
public WeakListener create(
ViewDataBinding viewDataBinding,
int localFieldId,
ReferenceQueue<ViewDataBinding> referenceQueue
) {
return new LiveDataListener(viewDataBinding, localFieldId, referenceQueue)
.getListener();
}
};
private static final CallbackRegistry.NotifierCallback<OnRebindCallback, ViewDataBinding, Void>
REBIND_NOTIFIER = new NotifierCallback<OnRebindCallback, ViewDataBinding, Void>() {
@Override
public void onNotifyCallback(OnRebindCallback callback, ViewDataBinding sender, int mode,
Void arg2) {
switch (mode) {
case REBIND:
if (!callback.onPreBind(sender)) {
sender.mRebindHalted = true;
}
break;
case HALTED:
callback.onCanceled(sender);
break;
case REBOUND:
callback.onBound(sender);
break;
}
}
};
private static final ReferenceQueue<ViewDataBinding> sReferenceQueue = new ReferenceQueue<>();
private static final OnAttachStateChangeListener ROOT_REATTACHED_LISTENER;
static {
if (VERSION.SDK_INT < VERSION_CODES.KITKAT) {
ROOT_REATTACHED_LISTENER = null;
} else {
ROOT_REATTACHED_LISTENER = new OnAttachStateChangeListener() {
@TargetApi(VERSION_CODES.KITKAT)
@Override
public void onViewAttachedToWindow(View v) {
// execute the pending bindings.
final ViewDataBinding binding = getBinding(v);
binding.mRebindRunnable.run();
v.removeOnAttachStateChangeListener(this);
}
@Override
public void onViewDetachedFromWindow(View v) {
}
};
}
}
/**
* Runnable executed on animation heartbeat to rebind the dirty Views.
*/
private final Runnable mRebindRunnable = new Runnable() {
@Override
public void run() {
synchronized (this) {
mPendingRebind = false;
}
processReferenceQueue();
if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
// Nested so that we don't get a lint warning in IntelliJ
if (!mRoot.isAttachedToWindow()) {
// Don't execute the pending bindings until the View
// is attached again.
mRoot.removeOnAttachStateChangeListener(ROOT_REATTACHED_LISTENER);
mRoot.addOnAttachStateChangeListener(ROOT_REATTACHED_LISTENER);
return;
}
}
executePendingBindings();
}
};
/**
* Flag indicates that there are pending bindings that need to be reevaluated.
*/
private boolean mPendingRebind = false;
/**
* Indicates that a onPreBind has stopped the executePendingBindings call.
*/
private boolean mRebindHalted = false;
/**
* The observed expressions.
*/
private WeakListener[] mLocalFieldObservers;
/**
* The root View that this Binding is associated with.
*/
private final View mRoot;
/**
* The collection of OnRebindCallbacks.
*/
private CallbackRegistry<OnRebindCallback, ViewDataBinding, Void> mRebindCallbacks;
/**
* Flag to prevent reentrant executePendingBinding calls.
*/
private boolean mIsExecutingPendingBindings;
// null api < 16
private Choreographer mChoreographer;
private final Choreographer.FrameCallback mFrameCallback;
// null api >= 16
private Handler mUIThreadHandler;
/**
* The DataBindingComponent used by this data binding. This is used for BindingAdapters
* that are instance methods to retrieve the class instance that implements the
* adapter.
*
* @hide
*/
protected final DataBindingComponent mBindingComponent;
/**
* If this ViewDataBinding is an include in another ViewDataBinding, this is the one
* that contains this. mContainingBinding is used to order executeBindings -- containing
* bindings should execute prior to included bindings.
*/
private ViewDataBinding mContainingBinding;
/**
* Track the LifecycleOwner set in {@link #setLifecycleOwner(LifecycleOwner)}. Set as
* Object so that the class can be compiled without requiring the LifecycleOwner dependency.
*/
private LifecycleOwner mLifecycleOwner;
/**
* Listener when mLifecycleOwner is non-null to allow delaying rebind calls while the
* status is less than started.
*/
private OnStartListener mOnStartListener;
/**
* When LiveData first registers for a change, it notifies immediately that there was a
* change. This flag identifies that we've just started observing LiveData and we should ignore
* the change notification.
*/
private boolean mInLiveDataRegisterObserver;
/**
* When StateFlow first collects for chances, it notifies immediately that there was a
* change. This flag identifies that we've just started observing StateFlow and we should ignore
* the change notification.
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
protected boolean mInStateFlowRegisterObserver;
/**
* Needed for backwards binary compatibility.
* b/122936785
* @hide
*/
protected ViewDataBinding(DataBindingComponent bindingComponent, View root, int localFieldCount) {
mBindingComponent = bindingComponent;
mLocalFieldObservers = new WeakListener[localFieldCount];
this.mRoot = root;
if (Looper.myLooper() == null) {
throw new IllegalStateException("DataBinding must be created in view's UI Thread");
}
if (USE_CHOREOGRAPHER) {
mChoreographer = Choreographer.getInstance();
mFrameCallback = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
mRebindRunnable.run();
}
};
} else {
mFrameCallback = null;
mUIThreadHandler = new Handler(Looper.myLooper());
}
}
/**
* @hide
*/
protected ViewDataBinding(Object bindingComponent, View root, int localFieldCount) {
this(checkAndCastToBindingComponent(bindingComponent), root, localFieldCount);
}
private static DataBindingComponent checkAndCastToBindingComponent(Object bindingComponent) {
if (bindingComponent == null) {
return null;
}
if (!(bindingComponent instanceof DataBindingComponent)) {
throw new IllegalArgumentException("The provided bindingComponent parameter must" +
" be an instance of DataBindingComponent. See " +
" https://issuetracker.google.com/issues/116541301 for details of why" +
" this parameter is not defined as DataBindingComponent");
}
return (DataBindingComponent) bindingComponent;
}
/**
* @hide
*/
protected void setRootTag(View view) {
view.setTag(R.id.dataBinding, this);
}
/**
* @hide
*/
protected void setRootTag(View[] views) {
for (View view : views) {
view.setTag(R.id.dataBinding, this);
}
}
/**
* @hide
*
* @return Returns the current build sdk
*/
public static int getBuildSdkInt() {
return SDK_INT;
}
/**
* Called when an observed object changes. Sets the appropriate dirty flag if applicable.
* @param localFieldId The index into mLocalFieldObservers that this Object resides in.
* @param object The object that has changed.
* @param fieldId The BR ID of the field being changed or _all if
* no specific field is being notified.
* @return true if this change should cause a change to the UI.
* @hide
*/
protected abstract boolean onFieldChange(int localFieldId, Object object, int fieldId);
/**
* Set a value value in the Binding class.
* <p>
* Typically, the developer will be able to call the subclass's set method directly. For
* example, if there is a variable <code>x</code> in the Binding, a <code>setX</code> method
* will be generated. However, there are times when the specific subclass of ViewDataBinding
* is unknown, so the generated method cannot be discovered without reflection. The
* setVariable call allows the values of variables to be set without reflection.
*
* @param variableId the BR id of the variable to be set. For example, if the variable is
* <code>x</code>, then variableId will be <code>BR.x</code>.
* @param value The new value of the variable to be set.
* @return <code>true</code> if the variable is declared or used in the binding or
* <code>false</code> otherwise.
*/
public abstract boolean setVariable(int variableId, @Nullable Object value);
/**
* Sets the {@link LifecycleOwner} that should be used for observing changes of
* LiveData in this binding. If a {@link LiveData} is in one of the binding expressions
* and no LifecycleOwner is set, the LiveData will not be observed and updates to it
* will not be propagated to the UI.
*
* When using Data Binding with Fragments, make sure to use Fragment.getViewLifecycleOwner().
* Using the Fragment as the LifecycleOwner might cause memory leaks since the Fragment's
* Lifecycle outlives the view Lifecycle.
* When using Data Binding with Activities, you can use the Activity as the LifecycleOwner.
*
* @param lifecycleOwner The LifecycleOwner that should be used for observing changes of
* LiveData in this binding.
*/
@MainThread
public void setLifecycleOwner(@Nullable LifecycleOwner lifecycleOwner) {
if (lifecycleOwner instanceof Fragment) {
Log.w("DataBinding", "Setting the fragment as the LifecycleOwner might cause"
+ " memory leaks because views lives shorter than the Fragment. Consider"
+ " using Fragment's view lifecycle");
}
if (mLifecycleOwner == lifecycleOwner) {
return;
}
if (mLifecycleOwner != null) {
mLifecycleOwner.getLifecycle().removeObserver(mOnStartListener);
}
mLifecycleOwner = lifecycleOwner;
if (lifecycleOwner != null) {
if (mOnStartListener == null) {
mOnStartListener = new OnStartListener(this);
}
lifecycleOwner.getLifecycle().addObserver(mOnStartListener);
}
for (WeakListener<?> weakListener : mLocalFieldObservers) {
if (weakListener != null) {
weakListener.setLifecycleOwner(lifecycleOwner);
}
}
}
/**
* Returns the current LifecycleOwner assigned to the binding.
* Might be null if not set.
*
* @return The current LifecycleOwner used by the binding.
*/
@Nullable
public LifecycleOwner getLifecycleOwner() {
return mLifecycleOwner;
}
/**
* Add a listener to be called when reevaluating dirty fields. This also allows automatic
* updates to be halted, but does not stop explicit calls to {@link #executePendingBindings()}.
*
* @param listener The listener to add.
*/
public void addOnRebindCallback(@NonNull OnRebindCallback listener) {
if (mRebindCallbacks == null) {
mRebindCallbacks = new CallbackRegistry<OnRebindCallback, ViewDataBinding, Void>(REBIND_NOTIFIER);
}
mRebindCallbacks.add(listener);
}
/**
* Removes a listener that was added in {@link #addOnRebindCallback(OnRebindCallback)}.
*
* @param listener The listener to remove.
*/
public void removeOnRebindCallback(@NonNull OnRebindCallback listener) {
if (mRebindCallbacks != null) {
mRebindCallbacks.remove(listener);
}
}
/**
* Evaluates the pending bindings, updating any Views that have expressions bound to
* modified variables. This <b>must</b> be run on the UI thread.
*/
public void executePendingBindings() {
if (mContainingBinding == null) {
executeBindingsInternal();
} else {
mContainingBinding.executePendingBindings();
}
}
/**
* Evaluates the pending bindings without executing the parent bindings.
*/
private void executeBindingsInternal() {
if (mIsExecutingPendingBindings) {
requestRebind();
return;
}
if (!hasPendingBindings()) {
return;
}
mIsExecutingPendingBindings = true;
mRebindHalted = false;
if (mRebindCallbacks != null) {
mRebindCallbacks.notifyCallbacks(this, REBIND, null);
// The onRebindListeners will change mPendingHalted
if (mRebindHalted) {
mRebindCallbacks.notifyCallbacks(this, HALTED, null);
}
}
if (!mRebindHalted) {
executeBindings();
if (mRebindCallbacks != null) {
mRebindCallbacks.notifyCallbacks(this, REBOUND, null);
}
}
mIsExecutingPendingBindings = false;
}
/**
* Calls executeBindingsInternal on the other ViewDataBinding
*
* @hide
*/
protected static void executeBindingsOn(ViewDataBinding other) {
other.executeBindingsInternal();
}
void forceExecuteBindings() {
executeBindings();
}
/**
* @hide
*/
protected abstract void executeBindings();
/**
* Invalidates all binding expressions and requests a new rebind to refresh UI.
*/
public abstract void invalidateAll();
/**
* Returns whether the UI needs to be refresh to represent the current data.
*
* @return true if any field has changed and the binding should be evaluated.
*/
public abstract boolean hasPendingBindings();
/**
* Removes binding listeners to expression variables.
*/
public void unbind() {
for (WeakListener weakListener : mLocalFieldObservers) {
if (weakListener != null) {
weakListener.unregister();
}
}
}
static ViewDataBinding getBinding(View v) {
if (v != null) {
return (ViewDataBinding) v.getTag(R.id.dataBinding);
}
return null;
}
/**
* Returns the outermost View in the layout file associated with the Binding. If this
* binding is for a merge layout file, this will return the first root in the merge tag.
*
* @return the outermost View in the layout file associated with the Binding.
*/
@NonNull
@Override
public View getRoot() {
return mRoot;
}
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
protected void handleFieldChange(int mLocalFieldId, Object object, int fieldId) {
if (mInLiveDataRegisterObserver || mInStateFlowRegisterObserver) {
// We're in LiveData or StateFlow registration, which always results in a field change
// that we can ignore. The value will be read immediately after anyway, so
// there is no need to be dirty.
return;
}
boolean result = onFieldChange(mLocalFieldId, object, fieldId);
if (result) {
requestRebind();
}
}
/**
* @hide
*/
protected boolean unregisterFrom(int localFieldId) {
WeakListener listener = mLocalFieldObservers[localFieldId];
if (listener != null) {
return listener.unregister();
}
return false;
}
/**
* @hide
*/
protected void requestRebind() {
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; // wait until lifecycle owner is started
}
}
synchronized (this) {
if (mPendingRebind) {
return;
}
mPendingRebind = true;
}
if (USE_CHOREOGRAPHER) {
mChoreographer.postFrameCallback(mFrameCallback);
} else {
mUIThreadHandler.post(mRebindRunnable);
}
}
}
/**
* @hide
*/
protected Object getObservedField(int localFieldId) {
WeakListener listener = mLocalFieldObservers[localFieldId];
if (listener == null) {
return null;
}
return listener.getTarget();
}
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
protected 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;//nothing to do, same object
}
unregisterFrom(localFieldId);
registerTo(localFieldId, observable, listenerCreator);
return true;
}
/**
* @hide
*/
protected boolean updateRegistration(int localFieldId, Observable observable) {
return updateRegistration(localFieldId, observable, CREATE_PROPERTY_LISTENER);
}
/**
* @hide
*/
protected boolean updateRegistration(int localFieldId, ObservableList observable) {
return updateRegistration(localFieldId, observable, CREATE_LIST_LISTENER);
}
/**
* @hide
*/
protected boolean updateRegistration(int localFieldId, ObservableMap observable) {
return updateRegistration(localFieldId, observable, CREATE_MAP_LISTENER);
}
/**
* @hide
*/
protected boolean updateLiveDataRegistration(int localFieldId, LiveData<?> observable) {
mInLiveDataRegisterObserver = true;
try {
return updateRegistration(localFieldId, observable, CREATE_LIVE_DATA_LISTENER);
} finally {
mInLiveDataRegisterObserver = false;
}
}
/**
* @hide
*/
protected void ensureBindingComponentIsNotNull(Class<?> oneExample) {
if (mBindingComponent == null) {
String errorMessage = "Required DataBindingComponent is null in class " +
getClass().getSimpleName() + ". A BindingAdapter in " +
oneExample.getCanonicalName() +
" is not static and requires an object to use, retrieved from the " +
"DataBindingComponent. If you don't use an inflation method taking a " +
"DataBindingComponent, use DataBindingUtil.setDefaultComponent or " +
"make all BindingAdapter methods static.";
throw new IllegalStateException(errorMessage);
}
}
/**
* @hide
*/
protected void registerTo(int localFieldId, Object observable,
CreateWeakListener listenerCreator) {
if (observable == null) {
return;
}
WeakListener listener = mLocalFieldObservers[localFieldId];
if (listener == null) {
listener = listenerCreator.create(this, localFieldId, sReferenceQueue);
mLocalFieldObservers[localFieldId] = listener;
if (mLifecycleOwner != null) {
listener.setLifecycleOwner(mLifecycleOwner);
}
}
listener.setTarget(observable);
}
/**
* @hide
*/
protected static ViewDataBinding bind(Object bindingComponent, View view, int layoutId) {
return DataBindingUtil.bind(
checkAndCastToBindingComponent(bindingComponent),
view,
layoutId);
}
/**
* Walks the view hierarchy under root and pulls out tagged Views, includes, and views with
* IDs into an Object[] that is returned. This is used to walk the view hierarchy once to find
* all bound and ID'd views.
*
* @param bindingComponent The binding component to use with this binding.
* @param root The root of the view hierarchy to walk.
* @param numBindings The total number of ID'd views, views with expressions, and includes
* @param includes The include layout information, indexed by their container's index.
* @param viewsWithIds Indexes of views that don't have tags, but have IDs.
* @return An array of size numBindings containing all Views in the hierarchy that have IDs
* (with elements in viewsWithIds), are tagged containing expressions, or the bindings for
* included layouts.
* @hide
*/
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;
}
/** @hide */
protected static boolean parse(String str, boolean fallback) {
if (str == null) {
return fallback;
}
return Boolean.parseBoolean(str);
}
/** @hide */
protected static byte parse(String str, byte fallback) {
try {
return Byte.parseByte(str);
} catch (NumberFormatException e) {
return fallback;
}
}
/** @hide */
protected static short parse(String str, short fallback) {
try {
return Short.parseShort(str);
} catch (NumberFormatException e) {
return fallback;
}
}
/** @hide */
protected static int parse(String str, int fallback) {
try {
return Integer.parseInt(str);
} catch (NumberFormatException e) {
return fallback;
}
}
/** @hide */
protected static long parse(String str, long fallback) {
try {
return Long.parseLong(str);
} catch (NumberFormatException e) {
return fallback;
}
}
/** @hide */
protected static float parse(String str, float fallback) {
try {
return Float.parseFloat(str);
} catch (NumberFormatException e) {
return fallback;
}
}
/** @hide */
protected static double parse(String str, double fallback) {
try {
return Double.parseDouble(str);
} catch (NumberFormatException e) {
return fallback;
}
}
/** @hide */
protected static char parse(String str, char fallback) {
if (str == null || str.isEmpty()) {
return fallback;
}
return str.charAt(0);
}
/** @hide */
protected static int getColorFromResource(View view, int resourceId) {
if (VERSION.SDK_INT >= VERSION_CODES.M) {
return view.getContext().getColor(resourceId);
} else {
return view.getResources().getColor(resourceId);
}
}
/** @hide */
protected static ColorStateList getColorStateListFromResource(View view, int resourceId) {
if (VERSION.SDK_INT >= VERSION_CODES.M) {
return view.getContext().getColorStateList(resourceId);
} else {
return view.getResources().getColorStateList(resourceId);
}
}
/** @hide */
protected static Drawable getDrawableFromResource(View view, int resourceId) {
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
return view.getContext().getDrawable(resourceId);
} else {
return view.getResources().getDrawable(resourceId);
}
}
/** @hide */
protected static <T> T getFromArray(T[] arr, int index) {
if (arr == null || index < 0 || index >= arr.length) {
return null;
}
return arr[index];
}
/** @hide */
protected static <T> void setTo(T[] arr, int index, T value) {
if (arr == null || index < 0 || index >= arr.length) {
return;
}
arr[index] = value;
}
/** @hide */
protected static boolean getFromArray(boolean[] arr, int index) {
if (arr == null || index < 0 || index >= arr.length) {
return false;
}
return arr[index];
}
/** @hide */
protected static void setTo(boolean[] arr, int index, boolean value) {
if (arr == null || index < 0 || index >= arr.length) {
return;
}
arr[index] = value;
}
/** @hide */
protected static byte getFromArray(byte[] arr, int index) {
if (arr == null || index < 0 || index >= arr.length) {
return 0;
}
return arr[index];
}
/** @hide */
protected static void setTo(byte[] arr, int index, byte value) {
if (arr == null || index < 0 || index >= arr.length) {
return;
}
arr[index] = value;
}
/** @hide */
protected static short getFromArray(short[] arr, int index) {
if (arr == null || index < 0 || index >= arr.length) {
return 0;
}
return arr[index];
}
/** @hide */
protected static void setTo(short[] arr, int index, short value) {
if (arr == null || index < 0 || index >= arr.length) {
return;
}
arr[index] = value;
}
/** @hide */
protected static char getFromArray(char[] arr, int index) {
if (arr == null || index < 0 || index >= arr.length) {
return 0;
}
return arr[index];
}
/** @hide */
protected static void setTo(char[] arr, int index, char value) {
if (arr == null || index < 0 || index >= arr.length) {
return;
}
arr[index] = value;
}
/** @hide */
protected static int getFromArray(int[] arr, int index) {
if (arr == null || index < 0 || index >= arr.length) {
return 0;
}
return arr[index];
}
/** @hide */
protected static void setTo(int[] arr, int index, int value) {
if (arr == null || index < 0 || index >= arr.length) {
return;
}
arr[index] = value;
}
/** @hide */
protected static long getFromArray(long[] arr, int index) {
if (arr == null || index < 0 || index >= arr.length) {
return 0;
}
return arr[index];
}
/** @hide */
protected static void setTo(long[] arr, int index, long value) {
if (arr == null || index < 0 || index >= arr.length) {
return;
}
arr[index] = value;
}
/** @hide */
protected static float getFromArray(float[] arr, int index) {
if (arr == null || index < 0 || index >= arr.length) {
return 0;
}
return arr[index];
}
/** @hide */
protected static void setTo(float[] arr, int index, float value) {
if (arr == null || index < 0 || index >= arr.length) {
return;
}
arr[index] = value;
}
/** @hide */
protected static double getFromArray(double[] arr, int index) {
if (arr == null || index < 0 || index >= arr.length) {
return 0;
}
return arr[index];
}
/** @hide */
protected static void setTo(double[] arr, int index, double value) {
if (arr == null || index < 0 || index >= arr.length) {
return;
}
arr[index] = value;
}
/** @hide */
protected static <T> T getFromList(List<T> list, int index) {
if (list == null || index < 0 || index >= list.size()) {
return null;
}
return list.get(index);
}
/** @hide */
protected static <T> void setTo(List<T> list, int index, T value) {
if (list == null || index < 0 || index >= list.size()) {
return;
}
list.set(index, value);
}
/** @hide */
protected static <T> T getFromList(SparseArray<T> list, int index) {
if (list == null || index < 0) {
return null;
}
return list.get(index);
}
/** @hide */
protected static <T> void setTo(SparseArray<T> list, int index, T value) {
if (list == null || index < 0 || index >= list.size()) {
return;
}
list.put(index, value);
}
/** @hide */
@TargetApi(VERSION_CODES.JELLY_BEAN)
protected static <T> T getFromList(LongSparseArray<T> list, int index) {
if (list == null || index < 0) {
return null;
}
return list.get(index);
}
/** @hide */
@TargetApi(VERSION_CODES.JELLY_BEAN)
protected static <T> void setTo(LongSparseArray<T> list, int index, T value) {
if (list == null || index < 0 || index >= list.size()) {
return;
}
list.put(index, value);
}
/** @hide */
protected static <T> T getFromList(androidx.collection.LongSparseArray<T> list, int index) {
if (list == null || index < 0) {
return null;
}
return list.get(index);
}
/** @hide */
protected static <T> void setTo(androidx.collection.LongSparseArray<T> list, int index,
T value) {
if (list == null || index < 0 || index >= list.size()) {
return;
}
list.put(index, value);
}
/** @hide */
protected static boolean getFromList(SparseBooleanArray list, int index) {
if (list == null || index < 0) {
return false;
}
return list.get(index);
}
/** @hide */
protected static void setTo(SparseBooleanArray list, int index, boolean value) {
if (list == null || index < 0 || index >= list.size()) {
return;
}
list.put(index, value);
}
/** @hide */
protected static int getFromList(SparseIntArray list, int index) {
if (list == null || index < 0) {
return 0;
}
return list.get(index);
}
/** @hide */
protected static void setTo(SparseIntArray list, int index, int value) {
if (list == null || index < 0 || index >= list.size()) {
return;
}
list.put(index, value);
}
/** @hide */
@TargetApi(VERSION_CODES.JELLY_BEAN_MR2)
protected static long getFromList(SparseLongArray list, int index) {
if (list == null || index < 0) {
return 0;
}
return list.get(index);
}
/** @hide */
@TargetApi(VERSION_CODES.JELLY_BEAN_MR2)
protected static void setTo(SparseLongArray list, int index, long value) {
if (list == null || index < 0 || index >= list.size()) {
return;
}
list.put(index, value);
}
/** @hide */
protected static <K, T> T getFrom(Map<K, T> map, K key) {
if (map == null) {
return null;
}
return map.get(key);
}
/** @hide */
protected static <K, T> void setTo(Map<K, T> map, K key, T value) {
if (map == null) {
return;
}
map.put(key, value);
}
/** @hide */
protected static void setBindingInverseListener(ViewDataBinding binder,
InverseBindingListener oldListener, PropertyChangedInverseListener listener) {
if (oldListener != listener) {
if (oldListener != null) {
binder.removeOnPropertyChangedCallback(
(PropertyChangedInverseListener) oldListener);
}
if (listener != null) {
binder.addOnPropertyChangedCallback(listener);
}
}
}
/** @hide */
protected static int safeUnbox(java.lang.Integer boxed) {
return boxed == null ? 0 : (int)boxed;
}
/** @hide */
protected static long safeUnbox(java.lang.Long boxed) {
return boxed == null ? 0L : (long)boxed;
}
/** @hide */
protected static short safeUnbox(java.lang.Short boxed) {
return boxed == null ? 0 : (short)boxed;
}
/** @hide */
protected static byte safeUnbox(java.lang.Byte boxed) {
return boxed == null ? 0 : (byte)boxed;
}
/** @hide */
protected static char safeUnbox(java.lang.Character boxed) {
return boxed == null ? '\u0000' : (char)boxed;
}
/** @hide */
protected static double safeUnbox(java.lang.Double boxed) {
return boxed == null ? 0.0 : (double)boxed;
}
/** @hide */
protected static float safeUnbox(java.lang.Float boxed) {
return boxed == null ? 0f : (float)boxed;
}
/** @hide */
protected static boolean safeUnbox(java.lang.Boolean boxed) {
return boxed == null ? false : (boolean)boxed;
}
/**
* Used internally to set the containing binding for an included binding to this.
*
* @hide
*/
protected void setContainedBinding(ViewDataBinding included) {
if (included != null) {
included.mContainingBinding = this;
}
}
/**
* Walks the view hierarchy under roots and pulls out tagged Views, includes, and views with
* IDs into an Object[] that is returned. This is used to walk the view hierarchy once to find
* all bound and ID'd views.
*
* @param bindingComponent The binding component to use with this binding.
* @param roots The root Views of the view hierarchy to walk. This is used with merge tags.
* @param numBindings The total number of ID'd views, views with expressions, and includes
* @param includes The include layout information, indexed by their container's index.
* @param viewsWithIds Indexes of views that don't have tags, but have IDs.
* @return An array of size numBindings containing all Views in the hierarchy that have IDs
* (with elements in viewsWithIds), are tagged containing expressions, or the bindings for
* included layouts.
* @hide
*/
protected static Object[] mapBindings(DataBindingComponent bindingComponent, View[] roots,
int numBindings, IncludedLayouts includes, SparseIntArray viewsWithIds) {
Object[] bindings = new Object[numBindings];
for (int i = 0; i < roots.length; i++) {
mapBindings(bindingComponent, roots[i], bindings, includes, viewsWithIds, true);
}
return bindings;
}
private static void mapBindings(DataBindingComponent bindingComponent, View view,
Object[] bindings, IncludedLayouts includes, SparseIntArray viewsWithIds,
boolean isRoot) {
final int indexInIncludes;
final ViewDataBinding existingBinding = getBinding(view);
if (existingBinding != null) {
return;
}
Object objTag = view.getTag();
final String tag = (objTag instanceof String) ? (String) objTag : null;
boolean isBound = false;
if (isRoot && tag != null && tag.startsWith("layout")) {
final int underscoreIndex = tag.lastIndexOf('_');
if (underscoreIndex > 0 && isNumeric(tag, underscoreIndex + 1)) {
final int index = parseTagInt(tag, underscoreIndex + 1);
if (bindings[index] == null) {
bindings[index] = view;
}
indexInIncludes = includes == null ? -1 : index;
isBound = true;
} else {
indexInIncludes = -1;
}
} else if (tag != null && tag.startsWith(BINDING_TAG_PREFIX)) {
int tagIndex = parseTagInt(tag, BINDING_NUMBER_START);
if (bindings[tagIndex] == null) {
bindings[tagIndex] = view;
}
isBound = true;
indexInIncludes = includes == null ? -1 : tagIndex;
} else {
// Not a bound view
indexInIncludes = -1;
}
if (!isBound) {
final int id = view.getId();
if (id > 0) {
int index;
if (viewsWithIds != null && (index = viewsWithIds.get(id, -1)) >= 0 &&
bindings[index] == null) {
bindings[index] = view;
}
}
}
if (view instanceof ViewGroup) {
final ViewGroup viewGroup = (ViewGroup) view;
final int count = viewGroup.getChildCount();
int minInclude = 0;
for (int i = 0; i < count; i++) {
final View child = viewGroup.getChildAt(i);
boolean isInclude = false;
if (indexInIncludes >= 0 && child.getTag() instanceof String) {
String childTag = (String) child.getTag();
if (childTag.endsWith("_0") &&
childTag.startsWith("layout") && childTag.indexOf('/') > 0) {
// This *could* be an include. Test against the expected includes.
int includeIndex = findIncludeIndex(childTag, minInclude,
includes, indexInIncludes);
if (includeIndex >= 0) {
isInclude = true;
minInclude = includeIndex + 1;
final int index = includes.indexes[indexInIncludes][includeIndex];
final int layoutId = includes.layoutIds[indexInIncludes][includeIndex];
int lastMatchingIndex = findLastMatching(viewGroup, i);
if (lastMatchingIndex == i) {
bindings[index] = DataBindingUtil.bind(bindingComponent, child,
layoutId);
} else {
final int includeCount = lastMatchingIndex - i + 1;
final View[] included = new View[includeCount];
for (int j = 0; j < includeCount; j++) {
included[j] = viewGroup.getChildAt(i + j);
}
bindings[index] = DataBindingUtil.bind(bindingComponent, included,
layoutId);
i += includeCount - 1;
}
}
}
}
if (!isInclude) {
mapBindings(bindingComponent, child, bindings, includes, viewsWithIds, false);
}
}
}
}
private static int findIncludeIndex(String tag, int minInclude,
IncludedLayouts included, int includedIndex) {
final int slashIndex = tag.indexOf('/');
final CharSequence layoutName = tag.subSequence(slashIndex + 1, tag.length() - 2);
final String[] layouts = included.layouts[includedIndex];
final int length = layouts.length;
for (int i = minInclude; i < length; i++) {
final String layout = layouts[i];
if (TextUtils.equals(layoutName, layout)) {
return i;
}
}
return -1;
}
private static int findLastMatching(ViewGroup viewGroup, int firstIncludedIndex) {
final View firstView = viewGroup.getChildAt(firstIncludedIndex);
final String firstViewTag = (String) firstView.getTag();
final String tagBase = firstViewTag.substring(0, firstViewTag.length() - 1); // don't include the "0"
final int tagSequenceIndex = tagBase.length();
final int count = viewGroup.getChildCount();
int max = firstIncludedIndex;
for (int i = firstIncludedIndex + 1; i < count; i++) {
final View view = viewGroup.getChildAt(i);
final Object objTag = view.getTag();
final String tag = objTag instanceof String ? (String) view.getTag() : null;
if (tag != null && tag.startsWith(tagBase)) {
if (tag.length() == firstViewTag.length() && tag.charAt(tag.length() - 1) == '0') {
return max; // Found another instance of the include
}
if (isNumeric(tag, tagSequenceIndex)) {
max = i;
}
}
}
return max;
}
private static boolean isNumeric(String tag, int startIndex) {
int length = tag.length();
if (length == startIndex) {
return false; // no numerals
}
for (int i = startIndex; i < length; i++) {
if (!Character.isDigit(tag.charAt(i))) {
return false;
}
}
return true;
}
/**
* Parse the tag without creating a new String object. This is fast and assumes the
* tag is in the correct format.
* @param str The tag string.
* @return The binding tag number parsed from the tag string.
*/
private static int parseTagInt(String str, int startIndex) {
final int end = str.length();
int val = 0;
for (int i = startIndex; i < end; i++) {
val *= 10;
char c = str.charAt(i);
val += (c - '0');
}
return val;
}
/**
* Polls sReferenceQueue to remove listeners on ViewDataBindings that have been collected.
*/
private static void processReferenceQueue() {
Reference<? extends ViewDataBinding> ref;
while ((ref = sReferenceQueue.poll()) != null) {
if (ref instanceof WeakListener) {
WeakListener listener = (WeakListener) ref;
listener.unregister();
}
}
}
/**
* Pass through inflate method for generated code that receives bindingComponent as an object.
* <p>
* Only called by the generated code.
* See b/116541301 for details.
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
protected static <T extends ViewDataBinding> T inflateInternal(
@NonNull LayoutInflater inflater, int layoutId, @Nullable ViewGroup parent,
boolean attachToParent, @Nullable Object bindingComponent) {
return DataBindingUtil.inflate(
inflater,
layoutId,
parent,
attachToParent,
checkAndCastToBindingComponent(bindingComponent)
);
}
private static class WeakPropertyListener extends Observable.OnPropertyChangedCallback
implements ObservableReference<Observable> {
final WeakListener<Observable> mListener;
public WeakPropertyListener(
ViewDataBinding binder,
int localFieldId,
ReferenceQueue<ViewDataBinding> referenceQueue
) {
mListener = new WeakListener<Observable>(binder, localFieldId, this, referenceQueue);
}
@Override
public WeakListener<Observable> getListener() {
return mListener;
}
@Override
public void addListener(Observable target) {
target.addOnPropertyChangedCallback(this);
}
@Override
public void removeListener(Observable target) {
target.removeOnPropertyChangedCallback(this);
}
@Override
public void setLifecycleOwner(LifecycleOwner lifecycleOwner) {
}
@Override
public void onPropertyChanged(Observable sender, int propertyId) {
ViewDataBinding binder = mListener.getBinder();
if (binder == null) {
return;
}
Observable obj = mListener.getTarget();
if (obj != sender) {
return; // notification from the wrong object?
}
binder.handleFieldChange(mListener.mLocalFieldId, sender, propertyId);
}
}
private static class WeakListListener extends ObservableList.OnListChangedCallback
implements ObservableReference<ObservableList> {
final WeakListener<ObservableList> mListener;
public WeakListListener(
ViewDataBinding binder,
int localFieldId,
ReferenceQueue<ViewDataBinding> referenceQueue
) {
mListener = new WeakListener<ObservableList>(
binder, localFieldId, this, referenceQueue
);
}
@Override
public void setLifecycleOwner(LifecycleOwner lifecycleOwner) {
}
@Override
public WeakListener<ObservableList> getListener() {
return mListener;
}
@Override
public void addListener(ObservableList target) {
target.addOnListChangedCallback(this);
}
@Override
public void removeListener(ObservableList target) {
target.removeOnListChangedCallback(this);
}
@Override
public void onChanged(ObservableList sender) {
ViewDataBinding binder = mListener.getBinder();
if (binder == null) {
return;
}
ObservableList target = mListener.getTarget();
if (target != sender) {
return; // We expect notifications only from sender
}
binder.handleFieldChange(mListener.mLocalFieldId, target, 0);
}
@Override
public void onItemRangeChanged(ObservableList sender, int positionStart, int itemCount) {
onChanged(sender);
}
@Override
public void onItemRangeInserted(ObservableList sender, int positionStart, int itemCount) {
onChanged(sender);
}
@Override
public void onItemRangeMoved(ObservableList sender, int fromPosition, int toPosition,
int itemCount) {
onChanged(sender);
}
@Override
public void onItemRangeRemoved(ObservableList sender, int positionStart, int itemCount) {
onChanged(sender);
}
}
private static class WeakMapListener extends ObservableMap.OnMapChangedCallback
implements ObservableReference<ObservableMap> {
final WeakListener<ObservableMap> mListener;
public WeakMapListener(
ViewDataBinding binder,
int localFieldId,
ReferenceQueue<ViewDataBinding> referenceQueue
) {
mListener = new WeakListener<ObservableMap>(
binder, localFieldId, this, referenceQueue
);
}
@Override
public void setLifecycleOwner(LifecycleOwner lifecycleOwner) {
}
@Override
public WeakListener<ObservableMap> getListener() {
return mListener;
}
@Override
public void addListener(ObservableMap target) {
target.addOnMapChangedCallback(this);
}
@Override
public void removeListener(ObservableMap target) {
target.removeOnMapChangedCallback(this);
}
@Override
public void onMapChanged(ObservableMap sender, Object key) {
ViewDataBinding binder = mListener.getBinder();
if (binder == null || sender != mListener.getTarget()) {
return;
}
binder.handleFieldChange(mListener.mLocalFieldId, sender, 0);
}
}
private static class LiveDataListener implements Observer,
ObservableReference<LiveData<?>> {
final WeakListener<LiveData<?>> mListener;
// keep this weak because listeners might leak, we don't want to leak the owner
// see: b/176886060
@Nullable
WeakReference<LifecycleOwner> mLifecycleOwnerRef = null;
public LiveDataListener(
ViewDataBinding binder,
int localFieldId,
ReferenceQueue<ViewDataBinding> referenceQueue
) {
mListener = new WeakListener(binder, localFieldId, this, referenceQueue);
}
@Nullable
private LifecycleOwner getLifecycleOwner() {
WeakReference<LifecycleOwner> ownerRef = this.mLifecycleOwnerRef;
if (ownerRef == null) {
return null;
}
return ownerRef.get();
}
@Override
public void setLifecycleOwner(@Nullable LifecycleOwner lifecycleOwner) {
LifecycleOwner previousOwner = getLifecycleOwner();
LifecycleOwner newOwner = lifecycleOwner;
LiveData<?> liveData = mListener.getTarget();
if (liveData != null) {
if (previousOwner != null) {
liveData.removeObserver(this);
}
if (newOwner != null) {
liveData.observe(newOwner, this);
}
}
if (newOwner != null) {
mLifecycleOwnerRef = new WeakReference<LifecycleOwner>(newOwner);
}
}
@Override
public WeakListener<LiveData<?>> getListener() {
return mListener;
}
@Override
public void addListener(LiveData<?> target) {
LifecycleOwner lifecycleOwner = getLifecycleOwner();
if (lifecycleOwner != null) {
target.observe(lifecycleOwner, this);
}
}
@Override
public void removeListener(LiveData<?> target) {
target.removeObserver(this);
}
@Override
public void onChanged(@Nullable Object o) {
ViewDataBinding binder = mListener.getBinder();
if (binder != null) {
binder.handleFieldChange(mListener.mLocalFieldId, mListener.getTarget(), 0);
}
}
}
/**
* This class is used by generated subclasses of {@link ViewDataBinding} to track the
* included layouts contained in the bound layout. This class is an implementation
* detail of how binding expressions are mapped to Views after inflation.
* @hide
*/
protected static class IncludedLayouts {
public final String[][] layouts;
public final int[][] indexes;
public final int[][] layoutIds;
public IncludedLayouts(int bindingCount) {
layouts = new String[bindingCount][];
indexes = new int[bindingCount][];
layoutIds = new int[bindingCount][];
}
public void setIncludes(int index, String[] layouts, int[] indexes, int[] layoutIds) {
this.layouts[index] = layouts;
this.indexes[index] = indexes;
this.layoutIds[index] = layoutIds;
}
}
/**
* This class is used by generated subclasses of {@link ViewDataBinding} to listen for
* changes on variables of Bindings. This is important for two-way data binding on variables
* in included Bindings.
* @hide
*/
protected static abstract class PropertyChangedInverseListener
extends Observable.OnPropertyChangedCallback implements InverseBindingListener {
final int mPropertyId;
public PropertyChangedInverseListener(int propertyId) {
mPropertyId = propertyId;
}
@Override
public void onPropertyChanged(Observable sender, int propertyId) {
if (propertyId == mPropertyId || propertyId == 0) {
onChange();
}
}
}
/**
* This class is used internally to handle Lifecycle events in ViewDataBinding. A
* LifecycleObserver class MUST be public.
*
* @hide
*/
static class OnStartListener implements LifecycleObserver {
final WeakReference<ViewDataBinding> mBinding;
private OnStartListener(ViewDataBinding binding) {
mBinding = new WeakReference<>(binding);
}
@OnLifecycleEvent(Lifecycle.Event.ON_START)
public void onStart() {
ViewDataBinding dataBinding = mBinding.get();
if (dataBinding != null) {
dataBinding.executePendingBindings();
}
}
}
}