All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.landawn.abacus.android.util.ObserverX Maven / Gradle / Ivy

There is a newer version: 1.10.1
Show newest version
/*
 * Copyright (C) 2017 HaiYang Li
 *
 * 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 com.landawn.abacus.android.util;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.TimeUnit;

import com.landawn.abacus.android.util.Async.UIExecutor;
import com.landawn.abacus.util.Immutable;
import com.landawn.abacus.util.N;
import com.landawn.abacus.util.Throwables;
import com.landawn.abacus.util.Timed;
import com.landawn.abacus.util.Tuple;
import com.landawn.abacus.util.Tuple.Tuple2;
import com.landawn.abacus.util.Tuple.Tuple3;
import com.landawn.abacus.util.Tuple.Tuple4;
import com.landawn.abacus.util.Tuple.Tuple5;
import com.landawn.abacus.util.Tuple.Tuple9;
import com.landawn.abacus.util.function.BiConsumer;
import com.landawn.abacus.util.function.Consumer;
import com.landawn.abacus.util.function.Function;
import com.landawn.abacus.util.function.Predicate;

import android.text.Editable;
import android.text.TextWatcher;
import android.view.DragEvent;
import android.view.KeyEvent;
import android.view.MenuItem;
import android.view.MenuItem.OnMenuItemClickListener;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnAttachStateChangeListener;
import android.view.View.OnClickListener;
import android.view.View.OnDragListener;
import android.view.View.OnFocusChangeListener;
import android.view.View.OnHoverListener;
import android.view.View.OnKeyListener;
import android.view.View.OnLayoutChangeListener;
import android.view.View.OnLongClickListener;
import android.view.View.OnScrollChangeListener;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.view.ViewGroup.OnHierarchyChangeListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AutoCompleteTextView;
import android.widget.SearchView;
import android.widget.SearchView.OnQueryTextListener;
import android.widget.TextView;

// TODO: Auto-generated Javadoc
/**
 *
 * @author Haiyang Li
 * @param 
 * @since 0.9
 */
@com.landawn.abacus.annotation.Immutable
public abstract class ObserverX extends com.landawn.abacus.util.Observer implements Immutable {

    ObserverX() {
        super();
    }

    /**
     *
     * @param 
     * @param 
     * @param view
     * @return
     */
    public static > ViewObserver of(final T view) {
        return new ViewObserver<>(view);
    }

    /**
     *
     * @param 
     * @param 
     * @param view
     * @return
     */
    public static > ViewGroupObserver of(final T view) {
        return new ViewGroupObserver<>(view);
    }

    /**
     *
     * @param 
     * @param 
     * @param view
     * @return
     */
    public static > TextViewObserver of(final T view) {
        return new TextViewObserver<>(view);
    }

    /**
     *
     * @param 
     * @param 
     * @param view
     * @return
     */
    public static > SearchViewObserver of(final T view) {
        return new SearchViewObserver<>(view);
    }

    /**
     *
     * @param 
     * @param 
     * @param view
     * @return
     */
    public static > AutoCompleteTextViewObserver of(final T view) {
        return new AutoCompleteTextViewObserver<>(view);
    }

    /**
     *
     * @param 
     * @param 
     * @param menuItem
     * @return
     */
    public static > MenuItemObserver of(final T menuItem) {
        return new MenuItemObserver<>(menuItem);
    }

    /**
     * The Class UIObserverBase.
     *
     * @param 
     * @param 
     */
    protected static abstract class UIObserverBase> extends ObserverX implements Disposable {

        /** The dispose actions. */
        final List disposeActions = new ArrayList<>();

        /** The is disposed. */
        boolean isDisposed = false;

        /**
         * Instantiates a new UI observer base.
         */
        protected UIObserverBase() {
        }

        /**
         *
         * @param intervalDurationInMillis
         * @return
         */
        @Override
        public O debounce(final long intervalDurationInMillis) {
            return (O) super.debounce(intervalDurationInMillis);
        }

        /**
         *
         * @param intervalDuration
         * @param unit
         * @return
         */
        @Override
        public O debounce(final long intervalDuration, final TimeUnit unit) {
            return (O) super.debounce(intervalDuration, unit);
        }

        /**
         *
         * @param intervalDurationInMillis
         * @return
         */
        @Override
        public O throttleFirst(final long intervalDurationInMillis) {
            return (O) super.throttleFirst(intervalDurationInMillis);
        }

        /**
         *
         * @param intervalDuration
         * @param unit
         * @return
         */
        @Override
        public O throttleFirst(final long intervalDuration, final TimeUnit unit) {
            return (O) super.throttleFirst(intervalDuration, unit);
        }

        /**
         *
         * @param intervalDurationInMillis
         * @return
         */
        @Override
        public O throttleLast(final long intervalDurationInMillis) {
            return (O) super.throttleLast(intervalDurationInMillis);
        }

        /**
         *
         * @param intervalDuration
         * @param unit
         * @return
         */
        @Override
        public O throttleLast(final long intervalDuration, final TimeUnit unit) {
            return (O) super.throttleLast(intervalDuration, unit);
        }

        /**
         *
         * @param delayInMillis
         * @return
         */
        @Override
        public O delay(final long delayInMillis) {
            return (O) super.delay(delayInMillis);
        }

        /**
         *
         * @param delay
         * @param unit
         * @return
         */
        @Override
        public O delay(final long delay, final TimeUnit unit) {
            return (O) super.delay(delay, unit);
        }

        /**
         *
         * @return
         * @deprecated Unsupported operation.
         */
        @Deprecated
        @Override
        public ObserverX> timeInterval() {
            throw new UnsupportedOperationException();
        }

        /**
         *
         * @return
         * @deprecated Unsupported operation.
         */
        @Deprecated
        @Override
        public ObserverX> timestamp() {
            throw new UnsupportedOperationException();
        }

        /**
         *
         * @param n
         * @return
         */
        @Override
        public O skip(final long n) {
            N.checkArgNotNegative(n, "n");

            return (O) super.skip(n);

        }

        /**
         *
         * @param maxSize
         * @return
         */
        @Override
        public O limit(final long maxSize) {
            return (O) super.limit(maxSize);
        }

        /*
         * Is it possible to cause memory leak by caching the previous values?
         */
        /**
         *
         * @return
         * @deprecated Unsupported operation.
         */
        @Deprecated
        @Override
        public ObserverX distinct() {
            throw new UnsupportedOperationException();
        }

        /*
         * Is it possible to cause memory leak by caching the previous values?
         */
        /**
         *
         * @param keyMapper
         * @return
         * @deprecated Unsupported operation.
         */
        @Deprecated
        @Override
        public ObserverX distinctBy(final Function keyMapper) {
            throw new UnsupportedOperationException();
        }

        /**
         *
         * @param filter
         * @return
         */
        @Override
        public O filter(final Predicate filter) {
            return (O) super.filter(filter);
        }

        /**
         *
         * @param 
         * @param map
         * @return
         * @deprecated Unsupported operation.
         */
        @Deprecated
        @Override
        public  ObserverX map(final Function map) {
            throw new UnsupportedOperationException();
        }

        /**
         *
         * @param 
         * @param map
         * @return
         * @deprecated Unsupported operation.
         */
        @Deprecated
        @Override
        public  ObserverX flatMap(final Function> map) {
            throw new UnsupportedOperationException();
        }

        /**
         *
         * @param timespan
         * @param unit
         * @return
         * @deprecated Unsupported operation.
         */
        @Deprecated
        @Override
        public ObserverX> buffer(final long timespan, final TimeUnit unit) {
            throw new UnsupportedOperationException();
        }

        /**
         *
         * @param timespan
         * @param unit
         * @param count
         * @return
         * @deprecated Unsupported operation.
         */
        @Deprecated
        @Override
        public ObserverX> buffer(final long timespan, final TimeUnit unit, final int count) {
            throw new UnsupportedOperationException();
        }

        /**
         *
         * @param timespan
         * @param timeskip
         * @param unit
         * @return
         * @deprecated Unsupported operation.
         */
        @Deprecated
        @Override
        public ObserverX> buffer(final long timespan, final long timeskip, final TimeUnit unit) {
            throw new UnsupportedOperationException();
        }

        /**
         *
         * @param timespan
         * @param timeskip
         * @param unit
         * @param count
         * @return
         * @deprecated Unsupported operation.
         */
        @Deprecated
        @Override
        public ObserverX> buffer(final long timespan, final long timeskip, final TimeUnit unit, final int count) {
            throw new UnsupportedOperationException();
        }

        /**
         *
         * @param action
         * @deprecated Unsupported operation.
         */
        @Deprecated
        @Override
        public void observe(Consumer action) {
            throw new UnsupportedOperationException();
        }

        /**
         *
         * @param action
         * @param onError
         * @deprecated Unsupported operation.
         */
        @Deprecated
        @Override
        public void observe(final Consumer action, final Consumer onError) {
            throw new UnsupportedOperationException();
        }

        /**
         *
         * @param action
         * @param onError
         * @param onComplete
         * @deprecated Unsupported operation.
         */
        @Deprecated
        @Override
        public void observe(final Consumer action, final Consumer onError, final Runnable onComplete) {
            throw new UnsupportedOperationException();
        }

        /**
         * Dispose.
         */
        @Override
        public void dispose() {
            if (isDisposed() == false) {
                isDisposed = true;
                if (N.notNullOrEmpty(disposeActions)) {
                    for (Runnable action : disposeActions) {
                        action.run();
                    }
                }
            }
        }

        /**
         * Checks if is disposed.
         *
         * @return true, if is disposed
         */
        @Override
        public boolean isDisposed() {
            return isDisposed;
        }
    }

    /**
     * An asynchronous update interface for receiving notifications
     * about View information as the View is constructed.
     *
     * @param 
     * @param 
     */
    public static class ViewObserver> extends UIObserverBase {

        /** The view. */
        final T _view;

        /**
         * This method is called when information about an View
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param view
         */
        ViewObserver(final T view) {
            this._view = view;
        }

        /**
         * This method is called when information about an View
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @return
         */
        public Disposable onClick(final OnClickListener onNext) {
            return onClick(onNext, CommonUtils.ON_ERROR_MISSING);
        }

        /**
         * This method is called when information about an View
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @param onError
         * @return
         */
        public Disposable onClick(final OnClickListener onNext, final Consumer onError) {
            return onClick(onNext, onError, CommonUtils.EMPTY_ACTION);
        }

        /**
         * This method is called when information about an View
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @param onError
         * @param onComplete
         * @return
         */
        public Disposable onClick(final OnClickListener onNext, final Consumer onError, final Runnable onComplete) {
            N.checkArgNotNull(onNext, "onNext");
            N.checkArgNotNull(onError, "onError");
            N.checkArgNotNull(onComplete, "onComplete");

            dispatcher.append(new DispatcherBase(onError, onComplete) {
                @Override
                public void onNext(Object param) {
                    final View tmp = (View) param;

                    if (CommonUtils.isUiThread()) {
                        onNext.onClick(tmp);
                    } else {
                        UIExecutor.execute(new Throwables.Runnable() {
                            @Override
                            public void run() {
                                onNext.onClick(tmp);
                            }
                        });
                    }
                }
            });

            _view.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View view) {
                    dispatcher.onNext(view);
                }
            });

            disposeActions.add(new Runnable() {
                @Override
                public void run() {
                    _view.setOnClickListener(null);
                }
            });

            return this;
        }

        /**
         * This method is called when information about an View
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @return
         */
        public Disposable onLongClick(final OnLongClickListener onNext) {
            return onLongClick(onNext, CommonUtils.ON_ERROR_MISSING);
        }

        /**
         * This method is called when information about an View
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @param onError
         * @return
         */
        public Disposable onLongClick(final OnLongClickListener onNext, final Consumer onError) {
            return onLongClick(onNext, onError, CommonUtils.EMPTY_ACTION);
        }

        /**
         * This method is called when information about an View
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @param onError
         * @param onComplete
         * @return
         */
        public Disposable onLongClick(final OnLongClickListener onNext, final Consumer onError, final Runnable onComplete) {
            N.checkArgNotNull(onNext, "onNext");
            N.checkArgNotNull(onError, "onError");
            N.checkArgNotNull(onComplete, "onComplete");

            dispatcher.append(new DispatcherBase(onError, onComplete) {
                @Override
                public void onNext(Object param) {
                    final View tmp = (View) param;

                    if (CommonUtils.isUiThread()) {
                        onNext.onLongClick(tmp);
                    } else {
                        UIExecutor.execute(new Throwables.Runnable() {
                            @Override
                            public void run() {
                                onNext.onLongClick(tmp);
                            }
                        });
                    }
                }
            });

            _view.setOnLongClickListener(new OnLongClickListener() {
                @Override
                public boolean onLongClick(View view) {
                    dispatcher.onNext(view);
                    return true;
                }
            });

            disposeActions.add(new Runnable() {
                @Override
                public void run() {
                    _view.setOnLongClickListener(null);
                }
            });

            return this;
        }

        /**
         * This method is called when information about an View
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @return
         */
        public Disposable onDrag(final OnDragListener onNext) {
            return onDrag(onNext, CommonUtils.ON_ERROR_MISSING);
        }

        /**
         * This method is called when information about an View
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @param onError
         * @return
         */
        public Disposable onDrag(final OnDragListener onNext, final Consumer onError) {
            return onDrag(onNext, onError, CommonUtils.EMPTY_ACTION);
        }

        /**
         * This method is called when information about an View
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @param onError
         * @param onComplete
         * @return
         */
        public Disposable onDrag(final OnDragListener onNext, final Consumer onError, final Runnable onComplete) {
            N.checkArgNotNull(onNext, "onNext");
            N.checkArgNotNull(onError, "onError");
            N.checkArgNotNull(onComplete, "onComplete");

            dispatcher.append(new DispatcherBase(onError, onComplete) {
                @Override
                public void onNext(Object param) {
                    final Tuple2 tmp = (Tuple2) param;

                    if (CommonUtils.isUiThread()) {
                        onNext.onDrag(tmp._1, tmp._2);
                    } else {
                        UIExecutor.execute(new Throwables.Runnable() {
                            @Override
                            public void run() {
                                onNext.onDrag(tmp._1, tmp._2);
                            }
                        });
                    }
                }
            });

            _view.setOnDragListener(new OnDragListener() {
                @Override
                public boolean onDrag(View view, DragEvent dragEvent) {
                    dispatcher.onNext(Tuple.of(view, dragEvent));
                    return true;
                }
            });

            disposeActions.add(new Runnable() {
                @Override
                public void run() {
                    _view.setOnDragListener(null);
                }
            });

            return this;
        }

        /**
         * This method is called when information about an View
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @return
         */
        public Disposable onTouch(final OnTouchListener onNext) {
            return onTouch(onNext, CommonUtils.ON_ERROR_MISSING);
        }

        /**
         * This method is called when information about an View
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @param onError
         * @return
         */
        public Disposable onTouch(final OnTouchListener onNext, final Consumer onError) {
            return onTouch(onNext, onError, CommonUtils.EMPTY_ACTION);
        }

        /**
         * This method is called when information about an View
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @param onError
         * @param onComplete
         * @return
         */
        public Disposable onTouch(final OnTouchListener onNext, final Consumer onError, final Runnable onComplete) {
            N.checkArgNotNull(onNext, "onNext");
            N.checkArgNotNull(onError, "onError");
            N.checkArgNotNull(onComplete, "onComplete");

            dispatcher.append(new DispatcherBase(onError, onComplete) {
                @Override
                public void onNext(Object param) {
                    final Tuple2 tmp = (Tuple2) param;

                    if (CommonUtils.isUiThread()) {
                        onNext.onTouch(tmp._1, tmp._2);
                    } else {
                        UIExecutor.execute(new Throwables.Runnable() {
                            @Override
                            public void run() {
                                onNext.onTouch(tmp._1, tmp._2);
                            }
                        });
                    }
                }
            });

            _view.setOnTouchListener(new OnTouchListener() {
                @Override
                public boolean onTouch(View view, MotionEvent dragEvent) {
                    dispatcher.onNext(Tuple.of(view, dragEvent));
                    return true;
                }
            });

            disposeActions.add(new Runnable() {
                @Override
                public void run() {
                    _view.setOnTouchListener(null);
                }
            });

            return this;
        }

        /**
         * This method is called when information about an View
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @return
         */
        public Disposable onFocusChange(final OnFocusChangeListener onNext) {
            return onFocusChange(onNext, CommonUtils.ON_ERROR_MISSING);
        }

        /**
         * This method is called when information about an View
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @param onError
         * @return
         */
        public Disposable onFocusChange(final OnFocusChangeListener onNext, final Consumer onError) {
            return onFocusChange(onNext, onError, CommonUtils.EMPTY_ACTION);
        }

        /**
         * This method is called when information about an View
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @param onError
         * @param onComplete
         * @return
         */
        public Disposable onFocusChange(final OnFocusChangeListener onNext, final Consumer onError, final Runnable onComplete) {
            N.checkArgNotNull(onNext, "onNext");
            N.checkArgNotNull(onError, "onError");
            N.checkArgNotNull(onComplete, "onComplete");

            dispatcher.append(new DispatcherBase(onError, onComplete) {
                @Override
                public void onNext(Object param) {
                    final Tuple2 tmp = (Tuple2) param;

                    if (CommonUtils.isUiThread()) {
                        onNext.onFocusChange(tmp._1, tmp._2);
                    } else {
                        UIExecutor.execute(new Throwables.Runnable() {
                            @Override
                            public void run() {
                                onNext.onFocusChange(tmp._1, tmp._2);
                            }
                        });
                    }
                }
            });

            _view.setOnFocusChangeListener(new OnFocusChangeListener() {
                @Override
                public void onFocusChange(View v, boolean hasFocus) {
                    dispatcher.onNext(Tuple.of(v, hasFocus));
                }
            });

            disposeActions.add(new Runnable() {
                @Override
                public void run() {
                    _view.setOnFocusChangeListener(null);
                }
            });

            return this;
        }

        /**
         * This method is called when information about an View
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @return
         */
        public Disposable onHover(final OnHoverListener onNext) {
            return onHover(onNext, CommonUtils.ON_ERROR_MISSING);
        }

        /**
         * This method is called when information about an View
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @param onError
         * @return
         */
        public Disposable onHover(final OnHoverListener onNext, final Consumer onError) {
            return onHover(onNext, onError, CommonUtils.EMPTY_ACTION);
        }

        /**
         * This method is called when information about an View
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @param onError
         * @param onComplete
         * @return
         */
        public Disposable onHover(final OnHoverListener onNext, final Consumer onError, final Runnable onComplete) {
            N.checkArgNotNull(onNext, "onNext");
            N.checkArgNotNull(onError, "onError");
            N.checkArgNotNull(onComplete, "onComplete");

            dispatcher.append(new DispatcherBase(onError, onComplete) {
                @Override
                public void onNext(Object param) {
                    final Tuple2 tmp = (Tuple2) param;

                    if (CommonUtils.isUiThread()) {
                        onNext.onHover(tmp._1, tmp._2);
                    } else {
                        UIExecutor.execute(new Throwables.Runnable() {
                            @Override
                            public void run() {
                                onNext.onHover(tmp._1, tmp._2);
                            }
                        });
                    }
                }
            });

            _view.setOnHoverListener(new OnHoverListener() {
                @Override
                public boolean onHover(View view, MotionEvent dragEvent) {
                    dispatcher.onNext(Tuple.of(view, dragEvent));
                    return true;
                }
            });

            disposeActions.add(new Runnable() {
                @Override
                public void run() {
                    _view.setOnHoverListener(null);
                }
            });

            return this;
        }

        /**
         * This method is called when information about an View
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @return
         */
        public Disposable onKey(final OnKeyListener onNext) {
            return onKey(onNext, CommonUtils.ON_ERROR_MISSING);
        }

        /**
         * This method is called when information about an View
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @param onError
         * @return
         */
        public Disposable onKey(final OnKeyListener onNext, final Consumer onError) {
            return onKey(onNext, onError, CommonUtils.EMPTY_ACTION);
        }

        /**
         * This method is called when information about an View
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @param onError
         * @param onComplete
         * @return
         */
        public Disposable onKey(final OnKeyListener onNext, final Consumer onError, final Runnable onComplete) {
            N.checkArgNotNull(onNext, "onNext");
            N.checkArgNotNull(onError, "onError");
            N.checkArgNotNull(onComplete, "onComplete");

            dispatcher.append(new DispatcherBase(onError, onComplete) {
                @Override
                public void onNext(Object param) {
                    final Tuple3 tmp = (Tuple3) param;

                    if (CommonUtils.isUiThread()) {
                        onNext.onKey(tmp._1, tmp._2, tmp._3);
                    } else {
                        UIExecutor.execute(new Throwables.Runnable() {
                            @Override
                            public void run() {
                                onNext.onKey(tmp._1, tmp._2, tmp._3);
                            }
                        });
                    }
                }
            });

            _view.setOnKeyListener(new OnKeyListener() {
                @Override
                public boolean onKey(View view, int keyCode, KeyEvent event) {
                    dispatcher.onNext(Tuple.of(view, keyCode, event));
                    return true;
                }
            });

            disposeActions.add(new Runnable() {
                @Override
                public void run() {
                    _view.setOnKeyListener(null);
                }
            });

            return this;
        }

        /**
         * This method is called when information about an View
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @return
         */
        public Disposable onViewAttachedToWindow(final Consumer onNext) {
            return onViewAttachedToWindow(onNext, CommonUtils.ON_ERROR_MISSING);
        }

        /**
         * This method is called when information about an View
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @param onError
         * @return
         */
        public Disposable onViewAttachedToWindow(final Consumer onNext, final Consumer onError) {
            return onViewAttachedToWindow(onNext, onError, CommonUtils.EMPTY_ACTION);
        }

        /**
         * This method is called when information about an View
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @param onError
         * @param onComplete
         * @return
         */
        public Disposable onViewAttachedToWindow(final Consumer onNext, final Consumer onError, final Runnable onComplete) {
            N.checkArgNotNull(onNext, "onNext");
            N.checkArgNotNull(onError, "onError");
            N.checkArgNotNull(onComplete, "onComplete");

            dispatcher.append(new DispatcherBase(onError, onComplete) {
                @Override
                public void onNext(Object param) {
                    final View tmp = (View) param;

                    if (CommonUtils.isUiThread()) {
                        onNext.accept(tmp);
                    } else {
                        UIExecutor.execute(new Throwables.Runnable() {
                            @Override
                            public void run() {
                                onNext.accept(tmp);
                            }
                        });
                    }
                }
            });

            final OnAttachStateChangeListener onAttachStateChangeListener = new OnAttachStateChangeListener() {
                @Override
                public void onViewAttachedToWindow(View view) {
                    dispatcher.onNext(view);
                }

                @Override
                public void onViewDetachedFromWindow(View view) {
                    // Do nothing
                }
            };

            _view.addOnAttachStateChangeListener(onAttachStateChangeListener);

            disposeActions.add(new Runnable() {
                @Override
                public void run() {
                    _view.removeOnAttachStateChangeListener(onAttachStateChangeListener);
                }
            });

            return this;
        }

        /**
         * This method is called when information about an View
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @return
         */
        public Disposable onViewDetachedFromWindow(final Consumer onNext) {
            return onViewDetachedFromWindow(onNext, CommonUtils.ON_ERROR_MISSING);
        }

        /**
         * This method is called when information about an View
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @param onError
         * @return
         */
        public Disposable onViewDetachedFromWindow(final Consumer onNext, final Consumer onError) {
            return onViewDetachedFromWindow(onNext, onError, CommonUtils.EMPTY_ACTION);
        }

        /**
         * This method is called when information about an View
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @param onError
         * @param onComplete
         * @return
         */
        public Disposable onViewDetachedFromWindow(final Consumer onNext, final Consumer onError, final Runnable onComplete) {
            N.checkArgNotNull(onNext, "onNext");
            N.checkArgNotNull(onError, "onError");
            N.checkArgNotNull(onComplete, "onComplete");

            dispatcher.append(new DispatcherBase(onError, onComplete) {
                @Override
                public void onNext(Object param) {
                    final View tmp = (View) param;

                    if (CommonUtils.isUiThread()) {
                        onNext.accept(tmp);
                    } else {
                        UIExecutor.execute(new Throwables.Runnable() {
                            @Override
                            public void run() {
                                onNext.accept(tmp);
                            }
                        });
                    }
                }
            });

            final OnAttachStateChangeListener onAttachStateChangeListener = new OnAttachStateChangeListener() {
                @Override
                public void onViewAttachedToWindow(View view) {
                    // Do nothing
                }

                @Override
                public void onViewDetachedFromWindow(View view) {
                    dispatcher.onNext(view);
                }
            };

            _view.addOnAttachStateChangeListener(onAttachStateChangeListener);

            disposeActions.add(new Runnable() {
                @Override
                public void run() {
                    _view.removeOnAttachStateChangeListener(onAttachStateChangeListener);
                }
            });

            return this;
        }

        /**
         * This method is called when information about an View
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @return
         */
        public Disposable onAttachStateChange(final OnAttachStateChangeListener onNext) {
            return onAttachStateChange(onNext, CommonUtils.ON_ERROR_MISSING);
        }

        /**
         * This method is called when information about an View
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @param onError
         * @return
         */
        public Disposable onAttachStateChange(final OnAttachStateChangeListener onNext, final Consumer onError) {
            return onAttachStateChange(onNext, onError, CommonUtils.EMPTY_ACTION);
        }

        /**
         * This method is called when information about an View
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @param onError
         * @param onComplete
         * @return
         */
        public Disposable onAttachStateChange(final OnAttachStateChangeListener onNext, final Consumer onError, final Runnable onComplete) {
            N.checkArgNotNull(onNext, "onNext");
            N.checkArgNotNull(onError, "onError");
            N.checkArgNotNull(onComplete, "onComplete");

            dispatcher.append(new DispatcherBase(onError, onComplete) {
                @Override
                public void onNext(Object param) {
                    final Tuple2 tmp = (Tuple2) param;

                    if (CommonUtils.isUiThread()) {
                        if (tmp._1 == 0) {
                            onNext.onViewAttachedToWindow(tmp._2);
                        } else {
                            onNext.onViewDetachedFromWindow(tmp._2);
                        }
                    } else {
                        UIExecutor.execute(new Throwables.Runnable() {
                            @Override
                            public void run() {
                                if (tmp._1 == 0) {
                                    onNext.onViewAttachedToWindow(tmp._2);
                                } else {
                                    onNext.onViewDetachedFromWindow(tmp._2);
                                }
                            }
                        });
                    }
                }
            });

            final OnAttachStateChangeListener onAttachStateChangeListener = new OnAttachStateChangeListener() {
                @Override
                public void onViewAttachedToWindow(View view) {
                    dispatcher.onNext(Tuple.of(0, view));
                }

                @Override
                public void onViewDetachedFromWindow(View view) {
                    dispatcher.onNext(Tuple.of(1, view));
                }
            };

            _view.addOnAttachStateChangeListener(onAttachStateChangeListener);

            disposeActions.add(new Runnable() {
                @Override
                public void run() {
                    _view.removeOnAttachStateChangeListener(onAttachStateChangeListener);
                }
            });

            return this;
        }

        /**
         * This method is called when information about an View
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @return
         */
        public Disposable onLayoutChange(final OnLayoutChangeListener onNext) {
            return onLayoutChange(onNext, CommonUtils.ON_ERROR_MISSING);
        }

        /**
         * This method is called when information about an View
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @param onError
         * @return
         */
        public Disposable onLayoutChange(final OnLayoutChangeListener onNext, final Consumer onError) {
            return onLayoutChange(onNext, onError, CommonUtils.EMPTY_ACTION);
        }

        /**
         * This method is called when information about an View
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @param onError
         * @param onComplete
         * @return
         */
        public Disposable onLayoutChange(final OnLayoutChangeListener onNext, final Consumer onError, final Runnable onComplete) {
            N.checkArgNotNull(onNext, "onNext");
            N.checkArgNotNull(onError, "onError");
            N.checkArgNotNull(onComplete, "onComplete");

            dispatcher.append(new DispatcherBase(onError, onComplete) {
                @Override
                public void onNext(Object param) {
                    final Tuple9 tmp = (Tuple9) param;

                    if (CommonUtils.isUiThread()) {
                        onNext.onLayoutChange(tmp._1, tmp._2, tmp._3, tmp._4, tmp._5, tmp._6, tmp._7, tmp._8, tmp._9);
                    } else {
                        UIExecutor.execute(new Throwables.Runnable() {
                            @Override
                            public void run() {
                                onNext.onLayoutChange(tmp._1, tmp._2, tmp._3, tmp._4, tmp._5, tmp._6, tmp._7, tmp._8, tmp._9);
                            }
                        });
                    }
                }
            });

            final OnLayoutChangeListener onAttachStateChangeListener = new OnLayoutChangeListener() {
                @SuppressWarnings("deprecation")
                @Override
                public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
                    dispatcher.onNext(Tuple.of(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom));
                }
            };

            _view.addOnLayoutChangeListener(onAttachStateChangeListener);

            disposeActions.add(new Runnable() {
                @Override
                public void run() {
                    _view.removeOnLayoutChangeListener(onAttachStateChangeListener);
                }
            });

            return this;
        }

        /**
         * This method is called when information about an View
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @return
         */
        public Disposable onLayoutChange(final Consumer> onNext) {
            return onLayoutChange(onNext, CommonUtils.ON_ERROR_MISSING);
        }

        /**
         * This method is called when information about an View
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @param onError
         * @return
         */
        public Disposable onLayoutChange(final Consumer> onNext,
                final Consumer onError) {
            return onLayoutChange(onNext, onError, CommonUtils.EMPTY_ACTION);
        }

        /**
         * This method is called when information about an View
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @param onError
         * @param onComplete
         * @return
         */
        public Disposable onLayoutChange(final Consumer> onNext,
                final Consumer onError, final Runnable onComplete) {
            N.checkArgNotNull(onNext, "onNext");
            N.checkArgNotNull(onError, "onError");
            N.checkArgNotNull(onComplete, "onComplete");

            dispatcher.append(new DispatcherBase(onError, onComplete) {
                @Override
                public void onNext(Object param) {
                    final Tuple9 tmp = (Tuple9) param;

                    if (CommonUtils.isUiThread()) {
                        onNext.accept(tmp);
                    } else {
                        UIExecutor.execute(new Throwables.Runnable() {
                            @Override
                            public void run() {
                                onNext.accept(tmp);
                            }
                        });
                    }
                }
            });

            final OnLayoutChangeListener onAttachStateChangeListener = new OnLayoutChangeListener() {
                @SuppressWarnings("deprecation")
                @Override
                public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
                    dispatcher.onNext(Tuple.of(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom));
                }
            };

            _view.addOnLayoutChangeListener(onAttachStateChangeListener);

            disposeActions.add(new Runnable() {
                @Override
                public void run() {
                    _view.removeOnLayoutChangeListener(onAttachStateChangeListener);
                }
            });

            return this;
        }

        /**
         * On scroll change.
         *
         * @param onNext
         * @return
         */
        public Disposable onScrollChange(final OnScrollChangeListener onNext) {
            return onScrollChange(onNext, CommonUtils.ON_ERROR_MISSING);
        }

        /**
         * On scroll change.
         *
         * @param onNext
         * @param onError
         * @return
         */
        public Disposable onScrollChange(final OnScrollChangeListener onNext, final Consumer onError) {
            return onScrollChange(onNext, onError, CommonUtils.EMPTY_ACTION);
        }

        /**
         * On scroll change.
         *
         * @param onNext
         * @param onError
         * @param onComplete
         * @return
         */
        public Disposable onScrollChange(final OnScrollChangeListener onNext, final Consumer onError, final Runnable onComplete) {
            N.checkArgNotNull(onNext, "onNext");
            N.checkArgNotNull(onError, "onError");
            N.checkArgNotNull(onComplete, "onComplete");

            dispatcher.append(new DispatcherBase(onError, onComplete) {
                @Override
                public void onNext(Object param) {
                    final Tuple5 tmp = (Tuple5) param;

                    if (CommonUtils.isUiThread()) {
                        onNext.onScrollChange(tmp._1, tmp._2, tmp._3, tmp._4, tmp._5);
                    } else {
                        UIExecutor.execute(new Throwables.Runnable() {
                            @Override
                            public void run() {
                                onNext.onScrollChange(tmp._1, tmp._2, tmp._3, tmp._4, tmp._5);
                            }
                        });
                    }
                }
            });

            _view.setOnScrollChangeListener(new OnScrollChangeListener() {
                @Override
                public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
                    dispatcher.onNext(Tuple.of(v, scrollX, scrollY, oldScrollX, oldScrollY));
                }
            });

            disposeActions.add(new Runnable() {
                @Override
                public void run() {
                    _view.setOnScrollChangeListener(null);
                }
            });

            return this;
        }

        /**
         * On scroll change.
         *
         * @param onNext
         * @return
         */
        public Disposable onScrollChange(final Consumer> onNext) {
            return onScrollChange(onNext, CommonUtils.ON_ERROR_MISSING);
        }

        /**
         * On scroll change.
         *
         * @param onNext
         * @param onError
         * @return
         */
        public Disposable onScrollChange(final Consumer> onNext,
                final Consumer onError) {
            return onScrollChange(onNext, onError, CommonUtils.EMPTY_ACTION);
        }

        /**
         * On scroll change.
         *
         * @param onNext
         * @param onError
         * @param onComplete
         * @return
         */
        public Disposable onScrollChange(final Consumer> onNext,
                final Consumer onError, final Runnable onComplete) {
            N.checkArgNotNull(onNext, "onNext");
            N.checkArgNotNull(onError, "onError");
            N.checkArgNotNull(onComplete, "onComplete");

            dispatcher.append(new DispatcherBase(onError, onComplete) {
                @Override
                public void onNext(Object param) {
                    final Tuple5 tmp = (Tuple5) param;

                    if (CommonUtils.isUiThread()) {
                        onNext.accept(tmp);
                    } else {
                        UIExecutor.execute(new Throwables.Runnable() {
                            @Override
                            public void run() {
                                onNext.accept(tmp);
                            }
                        });
                    }
                }
            });

            _view.setOnScrollChangeListener(new OnScrollChangeListener() {
                @Override
                public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
                    dispatcher.onNext(Tuple.of(v, scrollX, scrollY, oldScrollX, oldScrollY));
                }
            });

            disposeActions.add(new Runnable() {
                @Override
                public void run() {
                    _view.setOnScrollChangeListener(null);
                }
            });

            return this;
        }

    }

    /**
     * An asynchronous update interface for receiving notifications
     * about ViewGroup information as the ViewGroup is constructed.
     *
     * @param 
     * @param 
     */
    public static class ViewGroupObserver> extends ViewObserver> {

        /**
         * This method is called when information about an ViewGroup
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param view
         */
        ViewGroupObserver(final T view) {
            super(view);
        }

        /**
         * This method is called when information about an ViewGroup
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @return
         */
        public Disposable onChildViewAdded(final BiConsumer onNext) {
            return onChildViewAdded(onNext, CommonUtils.ON_ERROR_MISSING);
        }

        /**
         * This method is called when information about an ViewGroup
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @param onError
         * @return
         */
        public Disposable onChildViewAdded(final BiConsumer onNext, final Consumer onError) {
            return onChildViewAdded(onNext, onError, CommonUtils.EMPTY_ACTION);
        }

        /**
         * This method is called when information about an ViewGroup
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @param onError
         * @param onComplete
         * @return
         */
        public Disposable onChildViewAdded(final BiConsumer onNext, final Consumer onError,
                final Runnable onComplete) {
            N.checkArgNotNull(onNext, "onNext");
            N.checkArgNotNull(onError, "onError");
            N.checkArgNotNull(onComplete, "onComplete");

            dispatcher.append(new DispatcherBase(onError, onComplete) {
                @Override
                public void onNext(Object param) {
                    @SuppressWarnings("unchecked")
                    final Tuple2 tmp = (Tuple2) param;

                    if (CommonUtils.isUiThread()) {
                        onNext.accept(tmp._1, tmp._2);
                    } else {
                        UIExecutor.execute(new Throwables.Runnable() {
                            @Override
                            public void run() {
                                onNext.accept(tmp._1, tmp._2);
                            }
                        });
                    }
                }
            });

            _view.setOnHierarchyChangeListener(new OnHierarchyChangeListener() {
                @Override
                public void onChildViewAdded(View parent, View child) {
                    dispatcher.onNext(Tuple.of(parent, child));
                }

                @Override
                public void onChildViewRemoved(View parent, View child) {
                    // Do nothing
                }
            });

            disposeActions.add(new Runnable() {
                @Override
                public void run() {
                    _view.setOnHierarchyChangeListener(null);
                }
            });

            return this;
        }

        /**
         * This method is called when information about an ViewGroup
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @return
         */
        public Disposable onChildViewRemoved(final BiConsumer onNext) {
            return onChildViewRemoved(onNext, CommonUtils.ON_ERROR_MISSING);
        }

        /**
         * This method is called when information about an ViewGroup
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @param onError
         * @return
         */
        public Disposable onChildViewRemoved(final BiConsumer onNext, final Consumer onError) {
            return onChildViewRemoved(onNext, onError, CommonUtils.EMPTY_ACTION);
        }

        /**
         * This method is called when information about an ViewGroup
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @param onError
         * @param onComplete
         * @return
         */
        public Disposable onChildViewRemoved(final BiConsumer onNext, final Consumer onError,
                final Runnable onComplete) {
            N.checkArgNotNull(onNext, "onNext");
            N.checkArgNotNull(onError, "onError");
            N.checkArgNotNull(onComplete, "onComplete");

            dispatcher.append(new DispatcherBase(onError, onComplete) {
                @Override
                public void onNext(Object param) {
                    @SuppressWarnings("unchecked")
                    final Tuple2 tmp = (Tuple2) param;

                    if (CommonUtils.isUiThread()) {
                        onNext.accept(tmp._1, tmp._2);
                    } else {
                        UIExecutor.execute(new Throwables.Runnable() {
                            @Override
                            public void run() {
                                onNext.accept(tmp._1, tmp._2);
                            }
                        });
                    }
                }
            });

            _view.setOnHierarchyChangeListener(new OnHierarchyChangeListener() {
                @Override
                public void onChildViewAdded(View parent, View child) {
                    // Do nothing
                }

                @Override
                public void onChildViewRemoved(View parent, View child) {
                    dispatcher.onNext(Tuple.of(parent, child));
                }
            });

            disposeActions.add(new Runnable() {
                @Override
                public void run() {
                    _view.setOnHierarchyChangeListener(null);
                }
            });

            return this;
        }

        /**
         * This method is called when information about an ViewGroup
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @return
         */
        public Disposable onHierarchyChange(final OnHierarchyChangeListener onNext) {
            return onHierarchyChange(onNext, CommonUtils.ON_ERROR_MISSING);
        }

        /**
         * This method is called when information about an ViewGroup
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @param onError
         * @return
         */
        public Disposable onHierarchyChange(final OnHierarchyChangeListener onNext, final Consumer onError) {
            return onHierarchyChange(onNext, onError, CommonUtils.EMPTY_ACTION);
        }

        /**
         * This method is called when information about an ViewGroup
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @param onError
         * @param onComplete
         * @return
         */
        public Disposable onHierarchyChange(final OnHierarchyChangeListener onNext, final Consumer onError, final Runnable onComplete) {
            N.checkArgNotNull(onNext, "onNext");
            N.checkArgNotNull(onError, "onError");
            N.checkArgNotNull(onComplete, "onComplete");

            dispatcher.append(new DispatcherBase(onError, onComplete) {
                @Override
                public void onNext(Object param) {
                    @SuppressWarnings("unchecked")
                    final Tuple3 tmp = (Tuple3) param;

                    if (CommonUtils.isUiThread()) {
                        if (tmp._1 == 0) {
                            onNext.onChildViewAdded(tmp._2, tmp._3);
                        } else {
                            onNext.onChildViewRemoved(tmp._2, tmp._3);
                        }
                    } else {
                        UIExecutor.execute(new Throwables.Runnable() {
                            @Override
                            public void run() {
                                if (tmp._1 == 0) {
                                    onNext.onChildViewAdded(tmp._2, tmp._3);
                                } else {
                                    onNext.onChildViewRemoved(tmp._2, tmp._3);
                                }
                            }
                        });
                    }
                }
            });

            _view.setOnHierarchyChangeListener(new OnHierarchyChangeListener() {
                @Override
                public void onChildViewAdded(View parent, View child) {
                    dispatcher.onNext(Tuple.of(0, parent, child));
                }

                @Override
                public void onChildViewRemoved(View parent, View child) {
                    dispatcher.onNext(Tuple.of(1, parent, child));
                }
            });

            disposeActions.add(new Runnable() {
                @Override
                public void run() {
                    _view.setOnHierarchyChangeListener(null);
                }
            });

            return this;
        }
    }

    /**
     * An asynchronous update interface for receiving notifications
     * about TextView information as the TextView is constructed.
     *
     * @param 
     * @param 
     */
    public static class TextViewObserver> extends ViewObserver {

        /**
         * This method is called when information about an TextView
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param view
         */
        TextViewObserver(final T view) {
            super(view);
        }

        /**
         * This method is called when information about an TextView
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @return
         */
        public Disposable onTextChanged(final Consumer onNext) {
            return onTextChanged(onNext, CommonUtils.ON_ERROR_MISSING);
        }

        /**
         * This method is called when information about an TextView
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @param onError
         * @return
         */
        public Disposable onTextChanged(final Consumer onNext, final Consumer onError) {
            return onTextChanged(onNext, onError, CommonUtils.EMPTY_ACTION);
        }

        /**
         * This method is called when information about an TextView
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @param onError
         * @param onComplete
         * @return
         */
        public Disposable onTextChanged(final Consumer onNext, final Consumer onError, final Runnable onComplete) {
            N.checkArgNotNull(onNext, "onNext");
            N.checkArgNotNull(onError, "onError");
            N.checkArgNotNull(onComplete, "onComplete");

            dispatcher.append(new DispatcherBase(onError, onComplete) {
                @Override
                public void onNext(Object param) {
                    @SuppressWarnings("unchecked")
                    final String tmp = (String) param;

                    if (CommonUtils.isUiThread()) {
                        onNext.accept(tmp);
                    } else {
                        UIExecutor.execute(new Throwables.Runnable() {
                            @Override
                            public void run() {
                                onNext.accept(tmp);
                            }
                        });
                    }
                }
            });

            final TextWatcher textWatcher = new TextWatcher() {
                @Override
                public void beforeTextChanged(CharSequence s, int start, int count, int after) {
                    // Do nothing
                }

                @Override
                public void onTextChanged(CharSequence s, int start, int before, int count) {
                    dispatcher.onNext(s.toString());
                }

                @Override
                public void afterTextChanged(Editable s) {
                    // Do nothing
                }
            };

            _view.addTextChangedListener(textWatcher);

            disposeActions.add(new Runnable() {
                @Override
                public void run() {
                    _view.removeTextChangedListener(textWatcher);
                }
            });

            return this;
        }

        /**
         * This method is called when information about an TextView
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @return
         */
        public Disposable onTextChangged(final Consumer> onNext) {
            return onTextChangged(onNext, CommonUtils.ON_ERROR_MISSING);
        }

        /**
         * This method is called when information about an TextView
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @param onError
         * @return
         */
        public Disposable onTextChangged(final Consumer> onNext,
                final Consumer onError) {
            return onTextChangged(onNext, onError, CommonUtils.EMPTY_ACTION);
        }

        /**
         * This method is called when information about an TextView
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @param onError
         * @param onComplete
         * @return
         */
        public Disposable onTextChangged(final Consumer> onNext,
                final Consumer onError, final Runnable onComplete) {
            N.checkArgNotNull(onNext, "onNext");
            N.checkArgNotNull(onError, "onError");
            N.checkArgNotNull(onComplete, "onComplete");

            dispatcher.append(new DispatcherBase(onError, onComplete) {
                @Override
                public void onNext(Object param) {
                    @SuppressWarnings("unchecked")
                    final Tuple4 tmp = (Tuple4) param;

                    if (CommonUtils.isUiThread()) {
                        onNext.accept(tmp);
                    } else {
                        UIExecutor.execute(new Throwables.Runnable() {
                            @Override
                            public void run() {
                                onNext.accept(tmp);
                            }
                        });
                    }
                }
            });

            final TextWatcher textWatcher = new TextWatcher() {
                @Override
                public void beforeTextChanged(CharSequence s, int start, int count, int after) {
                    // Do nothing
                }

                @Override
                public void onTextChanged(CharSequence s, int start, int before, int count) {
                    dispatcher.onNext(Tuple.of(s, start, before, count));
                }

                @Override
                public void afterTextChanged(Editable s) {
                    // Do nothing
                }
            };

            _view.addTextChangedListener(textWatcher);

            disposeActions.add(new Runnable() {
                @Override
                public void run() {
                    _view.removeTextChangedListener(textWatcher);
                }
            });

            return this;
        }

        /**
         * This method is called when information about an TextView
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @return
         */
        public Disposable beforeTextChanged(final Consumer onNext) {
            return beforeTextChanged(onNext, CommonUtils.ON_ERROR_MISSING);
        }

        /**
         * This method is called when information about an TextView
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @param onError
         * @return
         */
        public Disposable beforeTextChanged(final Consumer onNext, final Consumer onError) {
            return beforeTextChanged(onNext, onError, CommonUtils.EMPTY_ACTION);
        }

        /**
         * This method is called when information about an TextView
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @param onError
         * @param onComplete
         * @return
         */
        public Disposable beforeTextChanged(final Consumer onNext, final Consumer onError, final Runnable onComplete) {
            N.checkArgNotNull(onNext, "onNext");
            N.checkArgNotNull(onError, "onError");
            N.checkArgNotNull(onComplete, "onComplete");

            dispatcher.append(new DispatcherBase(onError, onComplete) {
                @Override
                public void onNext(Object param) {
                    @SuppressWarnings("unchecked")
                    final String tmp = (String) param;

                    if (CommonUtils.isUiThread()) {
                        onNext.accept(tmp);
                    } else {
                        UIExecutor.execute(new Throwables.Runnable() {
                            @Override
                            public void run() {
                                onNext.accept(tmp);
                            }
                        });
                    }
                }
            });

            final TextWatcher textWatcher = new TextWatcher() {
                @Override
                public void beforeTextChanged(CharSequence s, int start, int before, int count) {
                    dispatcher.onNext(s.toString());
                }

                @Override
                public void onTextChanged(CharSequence s, int start, int count, int after) {
                    // Do nothing
                }

                @Override
                public void afterTextChanged(Editable s) {
                    // Do nothing
                }
            };

            _view.addTextChangedListener(textWatcher);

            disposeActions.add(new Runnable() {
                @Override
                public void run() {
                    _view.removeTextChangedListener(textWatcher);
                }
            });

            return this;
        }

        /**
         * This method is called when information about an TextView
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @return
         */
        public Disposable beforeTextChangged(final Consumer> onNext) {
            return beforeTextChangged(onNext, CommonUtils.ON_ERROR_MISSING);
        }

        /**
         * This method is called when information about an TextView
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @param onError
         * @return
         */
        public Disposable beforeTextChangged(final Consumer> onNext,
                final Consumer onError) {
            return beforeTextChangged(onNext, onError, CommonUtils.EMPTY_ACTION);
        }

        /**
         * This method is called when information about an TextView
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @param onError
         * @param onComplete
         * @return
         */
        public Disposable beforeTextChangged(final Consumer> onNext,
                final Consumer onError, final Runnable onComplete) {
            N.checkArgNotNull(onNext, "onNext");
            N.checkArgNotNull(onError, "onError");
            N.checkArgNotNull(onComplete, "onComplete");

            dispatcher.append(new DispatcherBase(onError, onComplete) {
                @Override
                public void onNext(Object param) {
                    @SuppressWarnings("unchecked")
                    final Tuple4 tmp = (Tuple4) param;

                    if (CommonUtils.isUiThread()) {
                        onNext.accept(tmp);
                    } else {
                        UIExecutor.execute(new Throwables.Runnable() {
                            @Override
                            public void run() {
                                onNext.accept(tmp);
                            }
                        });
                    }
                }
            });

            final TextWatcher textWatcher = new TextWatcher() {
                @Override
                public void beforeTextChanged(CharSequence s, int start, int before, int count) {
                    dispatcher.onNext(Tuple.of(s, start, before, count));
                }

                @Override
                public void onTextChanged(CharSequence s, int start, int count, int after) {
                    // Do nothing
                }

                @Override
                public void afterTextChanged(Editable s) {
                    // Do nothing
                }
            };

            _view.addTextChangedListener(textWatcher);

            disposeActions.add(new Runnable() {
                @Override
                public void run() {
                    _view.removeTextChangedListener(textWatcher);
                }
            });

            return this;
        }

        /**
         * This method is called when information about an TextView
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @return
         */
        public Disposable afterTextChanged(final Consumer onNext) {
            return afterTextChanged(onNext, CommonUtils.ON_ERROR_MISSING);
        }

        /**
         * This method is called when information about an TextView
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @param onError
         * @return
         */
        public Disposable afterTextChanged(final Consumer onNext, final Consumer onError) {
            return afterTextChanged(onNext, onError, CommonUtils.EMPTY_ACTION);
        }

        /**
         * This method is called when information about an TextView
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @param onError
         * @param onComplete
         * @return
         */
        public Disposable afterTextChanged(final Consumer onNext, final Consumer onError, final Runnable onComplete) {
            N.checkArgNotNull(onNext, "onNext");
            N.checkArgNotNull(onError, "onError");
            N.checkArgNotNull(onComplete, "onComplete");

            dispatcher.append(new DispatcherBase(onError, onComplete) {
                @Override
                public void onNext(Object param) {
                    @SuppressWarnings("unchecked")
                    final String tmp = (String) param;

                    if (CommonUtils.isUiThread()) {
                        onNext.accept(tmp);
                    } else {
                        UIExecutor.execute(new Throwables.Runnable() {
                            @Override
                            public void run() {
                                onNext.accept(tmp);
                            }
                        });
                    }
                }
            });

            final TextWatcher textWatcher = new TextWatcher() {
                @Override
                public void beforeTextChanged(CharSequence s, int start, int after, int count) {
                    // Do nothing
                }

                @Override
                public void onTextChanged(CharSequence s, int start, int count, int after) {
                    // Do nothing
                }

                @Override
                public void afterTextChanged(Editable s) {
                    dispatcher.onNext(s.toString());
                }
            };

            _view.addTextChangedListener(textWatcher);

            disposeActions.add(new Runnable() {
                @Override
                public void run() {
                    _view.removeTextChangedListener(textWatcher);
                }
            });

            return this;
        }

        /**
         * This method is called when information about an TextView
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @return
         */
        public Disposable afterTextChangged(final Consumer onNext) {
            return afterTextChangged(onNext, CommonUtils.ON_ERROR_MISSING);
        }

        /**
         * This method is called when information about an TextView
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @param onError
         * @return
         */
        public Disposable afterTextChangged(final Consumer onNext, final Consumer onError) {
            return afterTextChangged(onNext, onError, CommonUtils.EMPTY_ACTION);
        }

        /**
         * This method is called when information about an TextView
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @param onError
         * @param onComplete
         * @return
         */
        public Disposable afterTextChangged(final Consumer onNext, final Consumer onError, final Runnable onComplete) {
            N.checkArgNotNull(onNext, "onNext");
            N.checkArgNotNull(onError, "onError");
            N.checkArgNotNull(onComplete, "onComplete");

            dispatcher.append(new DispatcherBase(onError, onComplete) {
                @Override
                public void onNext(Object param) {
                    @SuppressWarnings("unchecked")
                    final Editable tmp = (Editable) param;

                    if (CommonUtils.isUiThread()) {
                        onNext.accept(tmp);
                    } else {
                        UIExecutor.execute(new Throwables.Runnable() {
                            @Override
                            public void run() {
                                onNext.accept(tmp);
                            }
                        });
                    }
                }
            });

            final TextWatcher textWatcher = new TextWatcher() {
                @Override
                public void beforeTextChanged(CharSequence s, int start, int after, int count) {
                    // Do nothing
                }

                @Override
                public void onTextChanged(CharSequence s, int start, int count, int after) {
                    // Do nothing
                }

                @Override
                public void afterTextChanged(Editable s) {
                    dispatcher.onNext(s);
                }
            };

            _view.addTextChangedListener(textWatcher);

            disposeActions.add(new Runnable() {
                @Override
                public void run() {
                    _view.removeTextChangedListener(textWatcher);
                }
            });

            return this;
        }

        /**
         * This method is called when information about an TextView
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @return
         */
        public Disposable onTextChanged(final TextWatcher onNext) {
            return onTextChanged(onNext, CommonUtils.ON_ERROR_MISSING);
        }

        /**
         * This method is called when information about an TextView
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @param onError
         * @return
         */
        public Disposable onTextChanged(final TextWatcher onNext, final Consumer onError) {
            return onTextChanged(onNext, onError, CommonUtils.EMPTY_ACTION);
        }

        /**
         * This method is called when information about an TextView
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @param onError
         * @param onComplete
         * @return
         */
        public Disposable onTextChanged(final TextWatcher onNext, final Consumer onError, final Runnable onComplete) {
            N.checkArgNotNull(onNext, "onNext");
            N.checkArgNotNull(onError, "onError");
            N.checkArgNotNull(onComplete, "onComplete");

            dispatcher.append(new DispatcherBase(onError, onComplete) {
                @Override
                public void onNext(final Object param) {
                    if (CommonUtils.isUiThread()) {
                        if (param instanceof Tuple5) {
                            final Tuple5 tmp = (Tuple5) param;

                            if (tmp._1 == 0) {
                                onNext.beforeTextChanged(tmp._2, tmp._3, tmp._4, tmp._5);
                            } else {
                                onNext.onTextChanged(tmp._2, tmp._3, tmp._4, tmp._5);
                            }
                        } else {
                            final Tuple2 tmp = (Tuple2) param;

                            onNext.afterTextChanged(tmp._2);
                        }
                    } else {
                        UIExecutor.execute(new Throwables.Runnable() {
                            @Override
                            public void run() {
                                if (param instanceof Tuple5) {
                                    final Tuple5 tmp = (Tuple5) param;

                                    if (tmp._1 == 0) {
                                        onNext.beforeTextChanged(tmp._2, tmp._3, tmp._4, tmp._5);
                                    } else {
                                        onNext.onTextChanged(tmp._2, tmp._3, tmp._4, tmp._5);
                                    }
                                } else {
                                    final Tuple2 tmp = (Tuple2) param;

                                    onNext.afterTextChanged(tmp._2);
                                }
                            }
                        });
                    }
                }
            });

            final TextWatcher textWatcher = new TextWatcher() {
                @Override
                public void beforeTextChanged(CharSequence s, int start, int count, int after) {
                    dispatcher.onNext(Tuple.of(0, s, start, count, after));
                }

                @Override
                public void onTextChanged(CharSequence s, int start, int before, int count) {
                    dispatcher.onNext(Tuple.of(1, s, start, before, count));
                }

                @Override
                public void afterTextChanged(Editable s) {
                    dispatcher.onNext(Tuple.of(2, s));
                }
            };

            _view.addTextChangedListener(textWatcher);

            disposeActions.add(new Runnable() {
                @Override
                public void run() {
                    _view.removeTextChangedListener(textWatcher);
                }
            });

            return this;
        }
    }

    /**
     * An asynchronous update interface for receiving notifications
     * about SearchView information as the SearchView is constructed.
     *
     * @param 
     * @param 
     */
    public static class SearchViewObserver> extends ViewGroupObserver {

        /**
         * This method is called when information about an SearchView
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param view
         */
        SearchViewObserver(final T view) {
            super(view);
        }

        /**
         * This method is called when information about an SearchView
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @return
         */
        public Disposable onQueryTextChange(final Consumer onNext) {
            return onQueryTextChange(onNext, CommonUtils.ON_ERROR_MISSING);
        }

        /**
         * This method is called when information about an SearchView
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @param onError
         * @return
         */
        public Disposable onQueryTextChange(final Consumer onNext, final Consumer onError) {
            return onQueryTextChange(onNext, onError, CommonUtils.EMPTY_ACTION);
        }

        /**
         * This method is called when information about an SearchView
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @param onError
         * @param onComplete
         * @return
         */
        public Disposable onQueryTextChange(final Consumer onNext, final Consumer onError, final Runnable onComplete) {
            N.checkArgNotNull(onNext, "onNext");
            N.checkArgNotNull(onError, "onError");
            N.checkArgNotNull(onComplete, "onComplete");

            dispatcher.append(new DispatcherBase(onError, onComplete) {
                @Override
                public void onNext(Object param) {
                    @SuppressWarnings("unchecked")
                    final String tmp = (String) param;

                    if (CommonUtils.isUiThread()) {
                        onNext.accept(tmp);
                    } else {
                        UIExecutor.execute(new Throwables.Runnable() {
                            @Override
                            public void run() {
                                onNext.accept(tmp);
                            }
                        });
                    }
                }
            });

            _view.setOnQueryTextListener(new OnQueryTextListener() {
                @Override
                public boolean onQueryTextChange(String newText) {
                    dispatcher.onNext(newText);
                    return true;
                }

                @Override
                public boolean onQueryTextSubmit(String query) {
                    // Do nothing.
                    return false;
                }
            });

            disposeActions.add(new Runnable() {
                @Override
                public void run() {
                    _view.setOnQueryTextListener(null);
                }
            });

            return this;
        }

        /**
         * This method is called when information about an SearchView
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @return
         */
        public Disposable onQueryTextSubmit(final Consumer onNext) {
            return onQueryTextSubmit(onNext, CommonUtils.ON_ERROR_MISSING);
        }

        /**
         * This method is called when information about an SearchView
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @param onError
         * @return
         */
        public Disposable onQueryTextSubmit(final Consumer onNext, final Consumer onError) {
            return onQueryTextSubmit(onNext, onError, CommonUtils.EMPTY_ACTION);
        }

        /**
         * This method is called when information about an SearchView
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @param onError
         * @param onComplete
         * @return
         */
        public Disposable onQueryTextSubmit(final Consumer onNext, final Consumer onError, final Runnable onComplete) {
            N.checkArgNotNull(onNext, "onNext");
            N.checkArgNotNull(onError, "onError");
            N.checkArgNotNull(onComplete, "onComplete");

            dispatcher.append(new DispatcherBase(onError, onComplete) {
                @Override
                public void onNext(Object param) {
                    @SuppressWarnings("unchecked")
                    final String tmp = (String) param;

                    if (CommonUtils.isUiThread()) {
                        onNext.accept(tmp);
                    } else {
                        UIExecutor.execute(new Throwables.Runnable() {
                            @Override
                            public void run() {
                                onNext.accept(tmp);
                            }
                        });
                    }
                }
            });

            _view.setOnQueryTextListener(new OnQueryTextListener() {
                @Override
                public boolean onQueryTextChange(String newText) {
                    // Do nothing.
                    return false;
                }

                @Override
                public boolean onQueryTextSubmit(String query) {
                    dispatcher.onNext(query);
                    return true;
                }
            });

            disposeActions.add(new Runnable() {
                @Override
                public void run() {
                    _view.setOnQueryTextListener(null);
                }
            });

            return this;
        }

        /**
         * This method is called when information about an SearchView
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @return
         */
        public Disposable onQueryText(final OnQueryTextListener onNext) {
            return onQueryText(onNext, CommonUtils.ON_ERROR_MISSING);
        }

        /**
         * This method is called when information about an SearchView
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @param onError
         * @return
         */
        public Disposable onQueryText(final OnQueryTextListener onNext, final Consumer onError) {
            return onQueryText(onNext, onError, CommonUtils.EMPTY_ACTION);
        }

        /**
         * This method is called when information about an SearchView
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @param onError
         * @param onComplete
         * @return
         */
        public Disposable onQueryText(final OnQueryTextListener onNext, final Consumer onError, final Runnable onComplete) {
            N.checkArgNotNull(onNext, "onNext");
            N.checkArgNotNull(onError, "onError");
            N.checkArgNotNull(onComplete, "onComplete");

            dispatcher.append(new DispatcherBase(onError, onComplete) {
                @Override
                public void onNext(Object param) {
                    @SuppressWarnings("unchecked")
                    final Tuple2 tmp = (Tuple2) param;

                    if (CommonUtils.isUiThread()) {
                        if (tmp._1 == 0) {
                            onNext.onQueryTextChange(tmp._2);
                        } else {
                            onNext.onQueryTextSubmit(tmp._2);
                        }
                    } else {
                        UIExecutor.execute(new Throwables.Runnable() {
                            @Override
                            public void run() {
                                if (tmp._1 == 0) {
                                    onNext.onQueryTextChange(tmp._2);
                                } else {
                                    onNext.onQueryTextSubmit(tmp._2);
                                }
                            }
                        });
                    }
                }
            });

            _view.setOnQueryTextListener(new OnQueryTextListener() {
                @Override
                public boolean onQueryTextChange(String newText) {
                    dispatcher.onNext(Tuple.of(0, newText));
                    return true;
                }

                @Override
                public boolean onQueryTextSubmit(String query) {
                    dispatcher.onNext(Tuple.of(1, query));
                    return true;
                }
            });

            disposeActions.add(new Runnable() {
                @Override
                public void run() {
                    _view.setOnQueryTextListener(null);
                }
            });

            return this;
        }
    }

    /**
     * An asynchronous update interface for receiving notifications
     * about AutoCompleteTextView information as the AutoCompleteTextView is constructed.
     *
     * @param 
     * @param 
     */
    public static class AutoCompleteTextViewObserver>
            extends TextViewObserver {

        /**
         * This method is called when information about an AutoCompleteTextView
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param view
         */
        AutoCompleteTextViewObserver(final T view) {
            super(view);
        }

        /**
         * This method is called when information about an AutoCompleteTextView
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @return
         */
        public Disposable onItemClick(final OnItemClickListener onNext) {
            return onItemClick(onNext, CommonUtils.ON_ERROR_MISSING);
        }

        /**
         * This method is called when information about an AutoCompleteTextView
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @param onError
         * @return
         */
        public Disposable onItemClick(final OnItemClickListener onNext, final Consumer onError) {
            return onItemClick(onNext, onError, CommonUtils.EMPTY_ACTION);
        }

        /**
         * This method is called when information about an AutoCompleteTextView
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @param onError
         * @param onComplete
         * @return
         */
        public Disposable onItemClick(final OnItemClickListener onNext, final Consumer onError, final Runnable onComplete) {
            N.checkArgNotNull(onNext, "onNext");
            N.checkArgNotNull(onError, "onError");
            N.checkArgNotNull(onComplete, "onComplete");

            dispatcher.append(new DispatcherBase(onError, onComplete) {
                @Override
                public void onNext(Object param) {
                    final Tuple4, View, Integer, Integer> tmp = (Tuple4, View, Integer, Integer>) param;

                    if (CommonUtils.isUiThread()) {
                        onNext.onItemClick(tmp._1, tmp._2, tmp._3, tmp._4);
                    } else {
                        UIExecutor.execute(new Throwables.Runnable() {
                            @Override
                            public void run() {
                                onNext.onItemClick(tmp._1, tmp._2, tmp._3, tmp._4);
                            }
                        });
                    }
                }
            });

            _view.setOnItemClickListener(new OnItemClickListener() {
                @Override
                public void onItemClick(AdapterView parent, View view, int position, long id) {
                    dispatcher.onNext(Tuple.of(parent, view, position, id));
                }
            });

            disposeActions.add(new Runnable() {
                @Override
                public void run() {
                    _view.setOnItemClickListener(null);
                }
            });

            return this;
        }

        /**
         * This method is called when information about an AutoCompleteTextView
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @return
         */
        public Disposable onItemClick(final Consumer, View, Integer, Integer>> onNext) {
            return onItemClick(onNext, CommonUtils.ON_ERROR_MISSING);
        }

        /**
         * This method is called when information about an AutoCompleteTextView
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @param onError
         * @return
         */
        public Disposable onItemClick(final Consumer, View, Integer, Integer>> onNext,
                final Consumer onError) {
            return onItemClick(onNext, onError, CommonUtils.EMPTY_ACTION);
        }

        /**
         * This method is called when information about an AutoCompleteTextView
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @param onError
         * @param onComplete
         * @return
         */
        public Disposable onItemClick(final Consumer, View, Integer, Integer>> onNext, final Consumer onError,
                final Runnable onComplete) {
            N.checkArgNotNull(onNext, "onNext");
            N.checkArgNotNull(onError, "onError");
            N.checkArgNotNull(onComplete, "onComplete");

            dispatcher.append(new DispatcherBase(onError, onComplete) {
                @Override
                public void onNext(Object param) {
                    final Tuple4, View, Integer, Integer> tmp = (Tuple4, View, Integer, Integer>) param;

                    if (CommonUtils.isUiThread()) {
                        onNext.accept(tmp);
                    } else {
                        UIExecutor.execute(new Throwables.Runnable() {
                            @Override
                            public void run() {
                                onNext.accept(tmp);
                            }
                        });
                    }
                }
            });

            _view.setOnItemClickListener(new OnItemClickListener() {
                @Override
                public void onItemClick(AdapterView parent, View view, int position, long id) {
                    dispatcher.onNext(Tuple.of(parent, view, position, id));
                }
            });

            disposeActions.add(new Runnable() {
                @Override
                public void run() {
                    _view.setOnItemClickListener(null);
                }
            });

            return this;
        }
    }

    /**
     * An asynchronous update interface for receiving notifications
     * about MenuItem information as the MenuItem is constructed.
     *
     * @param 
     * @param 
     */
    public static class MenuItemObserver> extends UIObserverBase {

        /** The menu item. */
        final MenuItem _menuItem;

        /**
         * This method is called when information about an MenuItem
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param menuItem
         */
        MenuItemObserver(final MenuItem menuItem) {
            this._menuItem = menuItem;
        }

        /**
         * This method is called when information about an MenuItem
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @return
         */
        public Disposable onMenuItemClick(final OnMenuItemClickListener onNext) {
            return onMenuItemClick(onNext, CommonUtils.ON_ERROR_MISSING);
        }

        /**
         * This method is called when information about an MenuItem
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @param onError
         * @return
         */
        public Disposable onMenuItemClick(final OnMenuItemClickListener onNext, final Consumer onError) {
            return onMenuItemClick(onNext, onError, CommonUtils.EMPTY_ACTION);
        }

        /**
         * This method is called when information about an MenuItem
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param onNext
         * @param onError
         * @param onComplete
         * @return
         */
        public Disposable onMenuItemClick(final OnMenuItemClickListener onNext, final Consumer onError, final Runnable onComplete) {
            N.checkArgNotNull(onNext, "onNext");
            N.checkArgNotNull(onError, "onError");
            N.checkArgNotNull(onComplete, "onComplete");

            dispatcher.append(new DispatcherBase(onError, onComplete) {
                @Override
                public void onNext(Object param) {
                    final MenuItem tmp = (MenuItem) param;

                    if (CommonUtils.isUiThread()) {
                        onNext.onMenuItemClick(tmp);
                    } else {
                        UIExecutor.execute(new Throwables.Runnable() {
                            @Override
                            public void run() {
                                onNext.onMenuItemClick(tmp);
                            }
                        });
                    }
                }
            });

            _menuItem.setOnMenuItemClickListener(new OnMenuItemClickListener() {
                @Override
                public boolean onMenuItemClick(MenuItem item) {
                    dispatcher.onNext(item);
                    return true;
                }
            });

            disposeActions.add(new Runnable() {
                @Override
                public void run() {
                    _menuItem.setOnMenuItemClickListener(null);
                }
            });

            return this;
        }
    }
}