ViewDataBinding.java revision 9bdb2415487832e88a05c7bd19391b05440b468e
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.databinding;
18
19import com.android.databinding.library.R;
20
21import android.annotation.TargetApi;
22import android.databinding.CallbackRegistry.NotifierCallback;
23import android.os.Build.VERSION;
24import android.os.Build.VERSION_CODES;
25import android.text.TextUtils;
26import android.util.SparseIntArray;
27import android.view.View;
28import android.view.View.OnAttachStateChangeListener;
29import android.view.ViewGroup;
30
31import java.lang.ref.WeakReference;
32
33public abstract class ViewDataBinding {
34
35    /**
36     * Instead of directly accessing Build.VERSION.SDK_INT, generated code uses this value so that
37     * we can test API dependent behavior.
38     */
39    static int SDK_INT = VERSION.SDK_INT;
40
41    private static final int REBIND = 1;
42    private static final int HALTED = 2;
43    private static final int REBOUND = 3;
44
45    /**
46     * Prefix for android:tag on Views with binding. The root View and include tags will not have
47     * android:tag attributes and will use ids instead.
48     */
49    public static final String BINDING_TAG_PREFIX = "binding_";
50
51    // The length of BINDING_TAG_PREFIX prevents calling length repeatedly.
52    private static final int BINDING_NUMBER_START = BINDING_TAG_PREFIX.length();
53
54    // ICS (v 14) fixes a leak when using setTag(int, Object)
55    private static final boolean USE_TAG_ID = DataBinderMapper.TARGET_MIN_SDK >= 14;
56
57    /**
58     * Method object extracted out to attach a listener to a bound Observable object.
59     */
60    private static final CreateWeakListener CREATE_PROPERTY_LISTENER = new CreateWeakListener() {
61        @Override
62        public WeakListener create(ViewDataBinding viewDataBinding, int localFieldId) {
63            return new WeakPropertyListener(viewDataBinding, localFieldId);
64        }
65    };
66
67    /**
68     * Method object extracted out to attach a listener to a bound ObservableList object.
69     */
70    private static final CreateWeakListener CREATE_LIST_LISTENER = new CreateWeakListener() {
71        @Override
72        public WeakListener create(ViewDataBinding viewDataBinding, int localFieldId) {
73            return new WeakListListener(viewDataBinding, localFieldId);
74        }
75    };
76
77    /**
78     * Method object extracted out to attach a listener to a bound ObservableMap object.
79     */
80    private static final CreateWeakListener CREATE_MAP_LISTENER = new CreateWeakListener() {
81        @Override
82        public WeakListener create(ViewDataBinding viewDataBinding, int localFieldId) {
83            return new WeakMapListener(viewDataBinding, localFieldId);
84        }
85    };
86
87    private static final CallbackRegistry.NotifierCallback<OnRebindCallback, ViewDataBinding, Void>
88        REBIND_NOTIFIER = new NotifierCallback<OnRebindCallback, ViewDataBinding, Void>() {
89        @Override
90        public void onNotifyCallback(OnRebindCallback callback, ViewDataBinding sender, int mode,
91                Void arg2) {
92            switch (mode) {
93                case REBIND:
94                    if (!callback.onPreBind(sender)) {
95                        sender.mRebindHalted = true;
96                    }
97                    break;
98                case HALTED:
99                    callback.onCanceled(sender);
100                    break;
101                case REBOUND:
102                    callback.onBound(sender);
103                    break;
104            }
105        }
106    };
107
108    private static final OnAttachStateChangeListener ROOT_REATTACHED_LISTENER;
109
110    static {
111        if (VERSION.SDK_INT < VERSION_CODES.KITKAT) {
112            ROOT_REATTACHED_LISTENER = null;
113        } else {
114            ROOT_REATTACHED_LISTENER = new OnAttachStateChangeListener() {
115                @TargetApi(VERSION_CODES.KITKAT)
116                @Override
117                public void onViewAttachedToWindow(View v) {
118                    // execute the pending bindings.
119                    final ViewDataBinding binding;
120                    if (USE_TAG_ID) {
121                        binding = (ViewDataBinding) v.getTag(R.id.dataBinding);
122                    } else {
123                        binding = (ViewDataBinding) v.getTag();
124                    }
125                    v.post(binding.mRebindRunnable);
126                    v.removeOnAttachStateChangeListener(this);
127                }
128
129                @Override
130                public void onViewDetachedFromWindow(View v) {
131                }
132            };
133        }
134    }
135
136    /**
137     * Runnable executed on animation heartbeat to rebind the dirty Views.
138     */
139    private Runnable mRebindRunnable = new Runnable() {
140        @Override
141        public void run() {
142            synchronized (this) {
143                mPendingRebind = false;
144            }
145            if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
146                // Nested so that we don't get a lint warning in IntelliJ
147                if (!mRoot.isAttachedToWindow()) {
148                    // Don't execute the pending bindings until the View
149                    // is attached again.
150                    mRoot.removeOnAttachStateChangeListener(ROOT_REATTACHED_LISTENER);
151                    mRoot.addOnAttachStateChangeListener(ROOT_REATTACHED_LISTENER);
152                    return;
153                }
154            }
155            executePendingBindings();
156        }
157    };
158
159    /**
160     * Flag indicates that there are pending bindings that need to be reevaluated.
161     */
162    private boolean mPendingRebind = false;
163
164    /**
165     * Indicates that a onPreBind has stopped the executePendingBindings call.
166     */
167    private boolean mRebindHalted = false;
168
169    /**
170     * The observed expressions.
171     */
172    private WeakListener[] mLocalFieldObservers;
173
174    /**
175     * The root View that this Binding is associated with.
176     */
177    private final View mRoot;
178
179    /**
180     * The collection of OnRebindCallbacks.
181     */
182    private CallbackRegistry<OnRebindCallback, ViewDataBinding, Void> mRebindCallbacks;
183
184    /**
185     * Flag to prevent reentrant executePendingBinding calls.
186     */
187    private boolean mIsExecutingPendingBindings;
188
189    protected ViewDataBinding(View root, int localFieldCount) {
190        mLocalFieldObservers = new WeakListener[localFieldCount];
191        this.mRoot = root;
192    }
193
194    protected void setRootTag(View view) {
195        if (USE_TAG_ID) {
196            view.setTag(R.id.dataBinding, this);
197        } else {
198            view.setTag(this);
199        }
200    }
201
202    protected void setRootTag(View[] views) {
203        if (USE_TAG_ID) {
204            for (View view : views) {
205                view.setTag(R.id.dataBinding, this);
206            }
207        } else {
208            for (View view : views) {
209                view.setTag(this);
210            }
211        }
212    }
213
214    public static int getBuildSdkInt() {
215        return SDK_INT;
216    }
217
218    /**
219     * Called when an observed object changes. Sets the appropriate dirty flag if applicable.
220     * @param localFieldId The index into mLocalFieldObservers that this Object resides in.
221     * @param object The object that has changed.
222     * @param fieldId The BR ID of the field being changed or _all if
223     *                no specific field is being notified.
224     * @return true if this change should cause a change to the UI.
225     */
226    protected abstract boolean onFieldChange(int localFieldId, Object object, int fieldId);
227
228    /**
229     * Set a value value in the Binding class.
230     * <p>
231     * Typically, the developer will be able to call the subclass's set method directly. For
232     * example, if there is a variable <code>x</code> in the Binding, a <code>setX</code> method
233     * will be generated. However, there are times when the specific subclass of ViewDataBinding
234     * is unknown, so the generated method cannot be discovered without reflection. The
235     * setVariable call allows the values of variables to be set without reflection.
236     *
237     * @param variableId the BR id of the variable to be set. For example, if the variable is
238     *                   <code>x</code>, then variableId will be <code>BR.x</code>.
239     * @param value The new value of the variable to be set.
240     * @return <code>true</code> if the variable exists in the binding or <code>false</code>
241     * otherwise.
242     */
243    public abstract boolean setVariable(int variableId, Object value);
244
245    /**
246     * Add a listener to be called when reevaluating dirty fields. This also allows automatic
247     * updates to be halted, but does not stop explicit calls to {@link #executePendingBindings()}.
248     *
249     * @param listener The listener to add.
250     */
251    public void addOnRebindCallback(OnRebindCallback listener) {
252        if (mRebindCallbacks == null) {
253            mRebindCallbacks = new CallbackRegistry<OnRebindCallback, ViewDataBinding, Void>(REBIND_NOTIFIER);
254        }
255        mRebindCallbacks.add(listener);
256    }
257
258    /**
259     * Removes a listener that was added in {@link #addOnRebindCallback(OnRebindCallback)}.
260     *
261     * @param listener The listener to remove.
262     */
263    public void removeOnRebindCallback(OnRebindCallback listener) {
264        if (mRebindCallbacks != null) {
265            mRebindCallbacks.remove(listener);
266        }
267    }
268
269    /**
270     * Evaluates the pending bindings, updating any Views that have expressions bound to
271     * modified variables. This <b>must</b> be run on the UI thread.
272     */
273    public void executePendingBindings() {
274        if (mIsExecutingPendingBindings) {
275            requestRebind();
276            return;
277        }
278        mIsExecutingPendingBindings = true;
279        mRebindHalted = false;
280        if (mRebindCallbacks != null) {
281            mRebindCallbacks.notifyCallbacks(this, REBIND, null);
282
283            // The onRebindListeners will change mPendingHalted
284            if (mRebindHalted) {
285                mRebindCallbacks.notifyCallbacks(this, HALTED, null);
286            }
287        }
288        if (!mRebindHalted) {
289            executeBindings();
290            if (mRebindCallbacks != null) {
291                mRebindCallbacks.notifyCallbacks(this, REBOUND, null);
292            }
293        }
294        mIsExecutingPendingBindings = false;
295    }
296
297    void forceExecuteBindings() {
298        executeBindings();
299    }
300
301    protected abstract void executeBindings();
302
303    /**
304     * Used internally to invalidate flags of included layouts.
305     */
306    public abstract void invalidateAll();
307
308    /**
309     * Removes binding listeners to expression variables.
310     */
311    public void unbind() {
312        for (WeakListener weakListener : mLocalFieldObservers) {
313            if (weakListener != null) {
314                weakListener.unregister();
315            }
316        }
317    }
318
319    @Override
320    protected void finalize() throws Throwable {
321        unbind();
322    }
323
324    /**
325     * Returns the outermost View in the layout file associated with the Binding.
326     * @return the outermost View in the layout file associated with the Binding.
327     */
328    public View getRoot() {
329        return mRoot;
330    }
331
332    private void handleFieldChange(int mLocalFieldId, Object object, int fieldId) {
333        boolean result = onFieldChange(mLocalFieldId, object, fieldId);
334        if (result) {
335            requestRebind();
336        }
337    }
338
339    protected boolean unregisterFrom(int localFieldId) {
340        WeakListener listener = mLocalFieldObservers[localFieldId];
341        if (listener != null) {
342            return listener.unregister();
343        }
344        return false;
345    }
346
347    protected void requestRebind() {
348        synchronized (this) {
349            if (mPendingRebind) {
350                return;
351            }
352            mPendingRebind = true;
353        }
354        if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
355            mRoot.postOnAnimation(mRebindRunnable);
356        } else {
357            mRoot.post(mRebindRunnable);
358        }
359    }
360
361    protected Object getObservedField(int localFieldId) {
362        WeakListener listener = mLocalFieldObservers[localFieldId];
363        if (listener == null) {
364            return null;
365        }
366        return listener.getTarget();
367    }
368
369    private boolean updateRegistration(int localFieldId, Object observable,
370            CreateWeakListener listenerCreator) {
371        if (observable == null) {
372            return unregisterFrom(localFieldId);
373        }
374        WeakListener listener = mLocalFieldObservers[localFieldId];
375        if (listener == null) {
376            registerTo(localFieldId, observable, listenerCreator);
377            return true;
378        }
379        if (listener.getTarget() == observable) {
380            return false;//nothing to do, same object
381        }
382        unregisterFrom(localFieldId);
383        registerTo(localFieldId, observable, listenerCreator);
384        return true;
385    }
386
387    protected boolean updateRegistration(int localFieldId, Observable observable) {
388        return updateRegistration(localFieldId, observable, CREATE_PROPERTY_LISTENER);
389    }
390
391    protected boolean updateRegistration(int localFieldId, ObservableList observable) {
392        return updateRegistration(localFieldId, observable, CREATE_LIST_LISTENER);
393    }
394
395    protected boolean updateRegistration(int localFieldId, ObservableMap observable) {
396        return updateRegistration(localFieldId, observable, CREATE_MAP_LISTENER);
397    }
398
399    protected void registerTo(int localFieldId, Object observable,
400            CreateWeakListener listenerCreator) {
401        if (observable == null) {
402            return;
403        }
404        WeakListener listener = mLocalFieldObservers[localFieldId];
405        if (listener == null) {
406            listener = listenerCreator.create(this, localFieldId);
407            mLocalFieldObservers[localFieldId] = listener;
408        }
409        listener.setTarget(observable);
410    }
411
412    /**
413     * Walks the view hierarchy under root and pulls out tagged Views, includes, and views with
414     * IDs into an Object[] that is returned. This is used to walk the view hierarchy once to find
415     * all bound and ID'd views.
416     *
417     * @param root The root of the view hierarchy to walk.
418     * @param numBindings The total number of ID'd views, views with expressions, and includes
419     * @param includes The include layout information, indexed by their container's index.
420     * @param viewsWithIds Indexes of views that don't have tags, but have IDs.
421     * @return An array of size numBindings containing all Views in the hierarchy that have IDs
422     * (with elements in viewsWithIds), are tagged containing expressions, or the bindings for
423     * included layouts.
424     */
425    protected static Object[] mapBindings(View root, int numBindings,
426            IncludedLayoutIndex[][] includes, SparseIntArray viewsWithIds) {
427        Object[] bindings = new Object[numBindings];
428        mapBindings(root, bindings, includes, viewsWithIds, true);
429        return bindings;
430    }
431
432    /**
433     * Walks the view hierarchy under roots and pulls out tagged Views, includes, and views with
434     * IDs into an Object[] that is returned. This is used to walk the view hierarchy once to find
435     * all bound and ID'd views.
436     *
437     * @param roots The root Views of the view hierarchy to walk. This is used with merge tags.
438     * @param numBindings The total number of ID'd views, views with expressions, and includes
439     * @param includes The include layout information, indexed by their container's index.
440     * @param viewsWithIds Indexes of views that don't have tags, but have IDs.
441     * @return An array of size numBindings containing all Views in the hierarchy that have IDs
442     * (with elements in viewsWithIds), are tagged containing expressions, or the bindings for
443     * included layouts.
444     */
445    protected static Object[] mapBindings(View[] roots, int numBindings,
446            IncludedLayoutIndex[][] includes, SparseIntArray viewsWithIds) {
447        Object[] bindings = new Object[numBindings];
448        for (int i = 0; i < roots.length; i++) {
449            mapBindings(roots[i], bindings, includes, viewsWithIds, true);
450        }
451        return bindings;
452    }
453
454    private static void mapBindings(View view, Object[] bindings,
455            IncludedLayoutIndex[][] includes, SparseIntArray viewsWithIds, boolean isRoot) {
456        final IncludedLayoutIndex[] includedLayoutIndexes;
457        final String tag = (String) view.getTag();
458        boolean isBound = false;
459        if (isRoot && tag != null && tag.startsWith("layout")) {
460            final int underscoreIndex = tag.lastIndexOf('_');
461            if (underscoreIndex > 0 && isNumeric(tag, underscoreIndex + 1)) {
462                final int index = parseTagInt(tag, underscoreIndex + 1);
463                bindings[index] = view;
464                includedLayoutIndexes = includes == null ? null : includes[index];
465                isBound = true;
466            } else {
467                includedLayoutIndexes = null;
468            }
469        } else if (tag != null && tag.startsWith(BINDING_TAG_PREFIX)) {
470            int tagIndex = parseTagInt(tag, BINDING_NUMBER_START);
471            bindings[tagIndex] = view;
472            isBound = true;
473            includedLayoutIndexes = includes == null ? null : includes[tagIndex];
474        } else {
475            // Not a bound view
476            includedLayoutIndexes = null;
477        }
478        if (!isBound) {
479            final int id = view.getId();
480            if (id > 0) {
481                int index;
482                if (viewsWithIds != null && (index = viewsWithIds.get(id, -1)) >= 0) {
483                    bindings[index] = view;
484                }
485            }
486        }
487
488        if (view instanceof  ViewGroup) {
489            final ViewGroup viewGroup = (ViewGroup) view;
490            final int count = viewGroup.getChildCount();
491            int minInclude = 0;
492            for (int i = 0; i < count; i++) {
493                final View child = viewGroup.getChildAt(i);
494                boolean isInclude = false;
495                if (includedLayoutIndexes != null) {
496                    String childTag = (String) child.getTag();
497                    if (childTag != null && childTag.endsWith("_0") &&
498                            childTag.startsWith("layout") && childTag.indexOf('/') > 0) {
499                        // This *could* be an include. Test against the expected includes.
500                        int includeIndex = findIncludeIndex(childTag, minInclude,
501                                includedLayoutIndexes);
502                        if (includeIndex >= 0) {
503                            isInclude = true;
504                            minInclude = includeIndex + 1;
505                            IncludedLayoutIndex include = includedLayoutIndexes[includeIndex];
506                            int lastMatchingIndex = findLastMatching(viewGroup, i);
507                            if (lastMatchingIndex == i) {
508                                bindings[include.index] = DataBindingUtil.bind(child,
509                                        include.layoutId);
510                            } else {
511                                final int includeCount =  lastMatchingIndex - i + 1;
512                                final View[] included = new View[includeCount];
513                                for (int j = 0; j < includeCount; j++) {
514                                    included[j] = viewGroup.getChildAt(i + j);
515                                }
516                                bindings[include.index] = DataBindingUtil.bind(included,
517                                        include.layoutId);
518                                i += includeCount - 1;
519                            }
520                        }
521                    }
522                }
523                if (!isInclude) {
524                    mapBindings(child, bindings, includes, viewsWithIds, false);
525                }
526            }
527        }
528    }
529
530    private static int findIncludeIndex(String tag, int minInclude,
531            IncludedLayoutIndex[] layoutIndexes) {
532        final int slashIndex = tag.indexOf('/');
533        final CharSequence layoutName = tag.subSequence(slashIndex + 1, tag.length() - 2);
534
535        final int length = layoutIndexes.length;
536        for (int i = minInclude; i < length; i++) {
537            final IncludedLayoutIndex layoutIndex = layoutIndexes[i];
538            if (TextUtils.equals(layoutName, layoutIndex.layout)) {
539                return i;
540            }
541        }
542        return -1;
543    }
544
545    private static int findLastMatching(ViewGroup viewGroup, int firstIncludedIndex) {
546        final View firstView = viewGroup.getChildAt(firstIncludedIndex);
547        final String firstViewTag = (String) firstView.getTag();
548        final String tagBase = firstViewTag.substring(0, firstViewTag.length() - 1); // don't include the "0"
549        final int tagSequenceIndex = tagBase.length();
550
551        final int count = viewGroup.getChildCount();
552        int max = firstIncludedIndex;
553        for (int i = firstIncludedIndex + 1; i < count; i++) {
554            final View view = viewGroup.getChildAt(i);
555            final String tag = (String) view.getTag();
556            if (tag != null && tag.startsWith(tagBase)) {
557                if (tag.length() == firstViewTag.length() && tag.charAt(tag.length() - 1) == '0') {
558                    return max; // Found another instance of the include
559                }
560                if (isNumeric(tag, tagSequenceIndex)) {
561                    max = i;
562                }
563            }
564        }
565        return max;
566    }
567
568    private static boolean isNumeric(String tag, int startIndex) {
569        int length = tag.length();
570        if (length == startIndex) {
571            return false; // no numerals
572        }
573        for (int i = startIndex; i < length; i++) {
574            if (!Character.isDigit(tag.charAt(i))) {
575                return false;
576            }
577        }
578        return true;
579    }
580
581    /**
582     * Parse the tag without creating a new String object. This is fast and assumes the
583     * tag is in the correct format.
584     * @param str The tag string.
585     * @return The binding tag number parsed from the tag string.
586     */
587    private static int parseTagInt(String str, int startIndex) {
588        final int end = str.length();
589        int val = 0;
590        for (int i = startIndex; i < end; i++) {
591            val *= 10;
592            char c = str.charAt(i);
593            val += (c - '0');
594        }
595        return val;
596    }
597
598    private static abstract class WeakListener<T> {
599        private final WeakReference<ViewDataBinding> mBinder;
600        protected final int mLocalFieldId;
601        private T mTarget;
602
603        public WeakListener(ViewDataBinding binder, int localFieldId) {
604            mBinder = new WeakReference<ViewDataBinding>(binder);
605            mLocalFieldId = localFieldId;
606        }
607
608        public void setTarget(T object) {
609            unregister();
610            mTarget = object;
611            if (mTarget != null) {
612                addListener(mTarget);
613            }
614        }
615
616        public boolean unregister() {
617            boolean unregistered = false;
618            if (mTarget != null) {
619                removeListener(mTarget);
620                unregistered = true;
621            }
622            mTarget = null;
623            return unregistered;
624        }
625
626        public T getTarget() {
627            return mTarget;
628        }
629
630        protected ViewDataBinding getBinder() {
631            ViewDataBinding binder = mBinder.get();
632            if (binder == null) {
633                unregister(); // The binder is dead
634            }
635            return binder;
636        }
637
638        protected abstract void addListener(T target);
639        protected abstract void removeListener(T target);
640    }
641
642    private static class WeakPropertyListener extends WeakListener<Observable>
643            implements OnPropertyChangedListener {
644        public WeakPropertyListener(ViewDataBinding binder, int localFieldId) {
645            super(binder, localFieldId);
646        }
647
648        @Override
649        protected void addListener(Observable target) {
650            target.addOnPropertyChangedListener(this);
651        }
652
653        @Override
654        protected void removeListener(Observable target) {
655            target.removeOnPropertyChangedListener(this);
656        }
657
658        @Override
659        public void onPropertyChanged(Observable sender, int fieldId) {
660            ViewDataBinding binder = getBinder();
661            if (binder == null) {
662                return;
663            }
664            Observable obj = getTarget();
665            if (obj != sender) {
666                return; // notification from the wrong object?
667            }
668            binder.handleFieldChange(mLocalFieldId, sender, fieldId);
669        }
670    }
671
672    private static class WeakListListener extends WeakListener<ObservableList>
673            implements OnListChangedListener {
674
675        public WeakListListener(ViewDataBinding binder, int localFieldId) {
676            super(binder, localFieldId);
677        }
678
679        @Override
680        public void onChanged() {
681            ViewDataBinding binder = getBinder();
682            if (binder == null) {
683                return;
684            }
685            ObservableList target = getTarget();
686            if (target == null) {
687                return; // We don't expect any notifications from null targets
688            }
689            binder.handleFieldChange(mLocalFieldId, target, 0);
690        }
691
692        @Override
693        public void onItemRangeChanged(int positionStart, int itemCount) {
694            onChanged();
695        }
696
697        @Override
698        public void onItemRangeInserted(int positionStart, int itemCount) {
699            onChanged();
700        }
701
702        @Override
703        public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
704            onChanged();
705        }
706
707        @Override
708        public void onItemRangeRemoved(int positionStart, int itemCount) {
709            onChanged();
710        }
711
712        @Override
713        protected void addListener(ObservableList target) {
714            target.addOnListChangedListener(this);
715        }
716
717        @Override
718        protected void removeListener(ObservableList target) {
719            target.removeOnListChangedListener(this);
720        }
721    }
722
723    private static class WeakMapListener extends WeakListener<ObservableMap>
724            implements OnMapChangedListener {
725        public WeakMapListener(ViewDataBinding binder, int localFieldId) {
726            super(binder, localFieldId);
727        }
728
729        @Override
730        protected void addListener(ObservableMap target) {
731            target.addOnMapChangedListener(this);
732        }
733
734        @Override
735        protected void removeListener(ObservableMap target) {
736            target.removeOnMapChangedListener(this);
737        }
738
739        @Override
740        public void onMapChanged(ObservableMap sender, Object key) {
741            ViewDataBinding binder = getBinder();
742            if (binder == null || sender != getTarget()) {
743                return;
744            }
745            binder.handleFieldChange(mLocalFieldId, sender, 0);
746        }
747    }
748
749    private interface CreateWeakListener {
750        WeakListener create(ViewDataBinding viewDataBinding, int localFieldId);
751    }
752
753    protected static class IncludedLayoutIndex {
754        public final String layout;
755        public final int index;
756        public final int layoutId;
757
758        public IncludedLayoutIndex(String layout, int index, int layoutId) {
759            this.layout = layout;
760            this.index = index;
761            this.layoutId = layoutId;
762        }
763    }
764}
765