/* This file is part of Reactive Cascade which is released under The MIT License. See license.md , https://github.com/futurice/cascade and http://reactivecascade.com for details. This is open source for the common good. Please contribute improvements by pull request or contact paulirotta@gmail.com */ package com.reactivecascade.reactive.ui; import android.content.Context; import android.support.annotation.CallSuper; import android.support.annotation.CheckResult; import android.support.annotation.IdRes; import android.support.annotation.IntRange; import android.support.annotation.LayoutRes; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.UiThread; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.Filter; import com.reactivecascade.functional.ImmutableValue; import com.reactivecascade.functional.RunnableAltFuture; import com.reactivecascade.i.IAltFuture; import com.reactivecascade.i.IAsyncOrigin; import com.reactivecascade.i.NotCallOrigin; import com.reactivecascade.util.RCLog; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Comparator; import java.util.List; import static android.R.attr.value; import static com.reactivecascade.Async.UI; /** * Manipulate an {@link android.widget.ArrayAdapter} safely from any thread * <p> * If a method is thread safe, it in __Async alternative is not provided and you should use the default * one. If an __Async version is provided here, use that from your single-threaded or multi-threaded model code. * <p> * When you add, remove etc the actions are completed in serial call order on the UI thread * <p> * Each method call will complete synchronously if called from the UI thread. * <p> * Each method call will return immediately and complete asynchronously if there is no return from. * The exception handling in these cases is asynchronous, a message to the system log. * <p> * Each method call will complete synchronously if there is a return from and it is not called * from the UI thread. If there is an exception such as {@link java.util.concurrent.TimeoutException} * because the UI thread is not responsive within the time limit specified for this <code>ConcurrentListAdapter</code>, * a {@link java.lang.RuntimeException} will be thrown. * <p> * Example: * <pre> * <code> * myConcurrentListAdapter.addAsync(from) * ; // Return immediately, completed asynchronously on UI thread or synchronously if called from UI thread * myConcurrentListAdapter.getItemAsync(0) * .subscribe( ..dosomething ..) * ; // Returned from completed synchronously, will wait for the UI thread to catch up if not called from the UI thread * </code> * </pre> */ @NotCallOrigin public class AltArrayAdapter<T> extends ArrayAdapter<T> implements IAsyncOrigin { private final ImmutableValue<String> mOrigin = RCLog.originAsync(); public AltArrayAdapter(@NonNull Context context, @LayoutRes int resource) { super(context, resource, 0, new ArrayList<>()); } public AltArrayAdapter(@NonNull Context context, @LayoutRes int resource, @IdRes int textViewResourceId) { super(context, resource, textViewResourceId, new ArrayList<>()); } public AltArrayAdapter(@NonNull Context context, @LayoutRes int resource, @NonNull T[] objects) { super(context, resource, 0, Arrays.asList(objects)); } public AltArrayAdapter(@NonNull Context context, @LayoutRes int resource, @IdRes int textViewResourceId, @NonNull T[] objects) { super(context, resource, textViewResourceId, Arrays.asList(objects)); } public AltArrayAdapter(@NonNull Context context, @LayoutRes int resource, @NonNull List<T> objects) { super(context, resource, 0, objects); } public AltArrayAdapter(@NonNull Context context, @LayoutRes int resource, @IdRes int textViewResourceId, @NonNull List<T> objects) { super(context, resource, textViewResourceId, objects); } @NonNull public static AltArrayAdapter<CharSequence> createFromResource( @NonNull Context context, /*TODO @LayoutRes*/ int textArrayResId, /*TODO @IdRes*/ int textViewResId) { CharSequence[] strings = context.getResources().getTextArray(textArrayResId); return new AltArrayAdapter<>(context, textViewResId, strings); } @CallSuper @Override @UiThread public void add(@NonNull T value) { super.add(value); RCLog.v(mOrigin, "Add to AltArrayAdapter: " + value); } /** * Add the from to the end of the list * * @param value * @return */ @CallSuper @NonNull @CheckResult(suggest = IAltFuture.CHECK_RESULT_SUGGESTION) public IAltFuture<?, T> addAsync(@NonNull T value, boolean ifAbsent) { return UI.then(() -> { if (ifAbsent) { remove(value); } add(value); return value; }); } @CallSuper @NotCallOrigin public void remove(@NonNull T value) { super.remove(value); RCLog.v(mOrigin, "Remove from AltArrayAdapter: " + value); } /** * Remove the item from the list * * @param object * @return */ @CallSuper @NonNull @CheckResult(suggest = IAltFuture.CHECK_RESULT_SUGGESTION) public IAltFuture<?, T> removeAsync(@NonNull T object) { return new RunnableAltFuture<>(UI, () -> { remove(object); return object; }); } /** * Sort the display list * * @param comparator * @param <A> * @return */ @CallSuper @NonNull @CheckResult(suggest = IAltFuture.CHECK_RESULT_SUGGESTION) public <A> IAltFuture<A, A> sortAsync(@NonNull Comparator<? super T> comparator) { RCLog.v(mOrigin, "Sort AltArrayAdapter: " + comparator); return new RunnableAltFuture<>(UI, () -> sort(comparator)); } /** * Although the underlying call is thread safe to call directly, we always want this to * run after any pending changes on the UI thread. * * @param <A> the upstream output from type * @return the output from from upstream */ @CallSuper @NonNull @CheckResult(suggest = IAltFuture.CHECK_RESULT_SUGGESTION) public <A> IAltFuture<A, A> notifyDataSetChangedAsync() { return new RunnableAltFuture<>(UI, this::notifyDataSetChanged); } /** * Although the underlying call is thread safe to call directly, we always want this to * run after any pending changes on the UI thread. * * @param <A> the upstream output from type * @return the output from from upstream */ @CallSuper @NonNull @CheckResult(suggest = IAltFuture.CHECK_RESULT_SUGGESTION) public <A> IAltFuture<A, A> notifyDataSetInvalidatedAsync() { return new RunnableAltFuture<>(UI, this::notifyDataSetInvalidated); } @CallSuper @NonNull @CheckResult(suggest = IAltFuture.CHECK_RESULT_SUGGESTION) public <A> IAltFuture<A, A> setNotifyOnChangeAsync(final boolean notifyOnChange) { return new RunnableAltFuture<>(UI, () -> setNotifyOnChange(notifyOnChange)); } @CallSuper @NonNull @CheckResult(suggest = IAltFuture.CHECK_RESULT_SUGGESTION) public <A, TT extends T> IAltFuture<A, A> addAllAsync(@NonNull Collection<TT> collection, boolean addIfUnique) { RCLog.v(mOrigin, "Add all async to AltArrayAdapter: addCount=" + collection.size()); if (addIfUnique) { return new RunnableAltFuture<>(UI, () -> { for (final TT t : collection) { remove(t); add(t); } }); } return new RunnableAltFuture<>(UI, () -> addAll(collection)); } @SafeVarargs @CallSuper @NonNull @CheckResult(suggest = IAltFuture.CHECK_RESULT_SUGGESTION) public final <TT extends T> IAltFuture<?, List<TT>> addAllAsync(@NonNull TT... items) { ArrayList<TT> list = new ArrayList<>(items.length); for (final TT item : items) { add(item); } return addAllAsync(list, false); } @CallSuper @NonNull @CheckResult(suggest = IAltFuture.CHECK_RESULT_SUGGESTION) public <A> IAltFuture<A, A> clearAsync() { return new RunnableAltFuture<>(UI, this::clear); } @CallSuper @IntRange(from = 0, to = Integer.MAX_VALUE) public int getCount() { return super.getCount(); } @CallSuper @NonNull @CheckResult(suggest = IAltFuture.CHECK_RESULT_SUGGESTION) public IAltFuture<?, Integer> getCountAsync() { return new RunnableAltFuture<>(UI, this::getCount); } @CallSuper @NonNull @CheckResult(suggest = IAltFuture.CHECK_RESULT_SUGGESTION) public IAltFuture<?, List<T>> getAllAsync() { return new RunnableAltFuture<>(UI, this::getAll); } @NonNull @UiThread public List<T> getAll() { int n = getCount(); List<T> list = new ArrayList<>(n); for (int i = 0; i < n; i++) { list.add(getItem(i)); } return list; } @CallSuper @NonNull public T getItem(@IntRange(from = 0, to = Integer.MAX_VALUE) int position) { return super.getItem(position); } @CallSuper @NonNull @CheckResult(suggest = IAltFuture.CHECK_RESULT_SUGGESTION) public IAltFuture<?, T> getItemAsync(@IntRange(from = 0, to = Integer.MAX_VALUE) int position) { return new RunnableAltFuture<>(UI, () -> getItem(position)); } @CallSuper @IntRange(from = -1, to = Integer.MAX_VALUE) public int getPosition(@NonNull T item) { return super.getPosition(item); } /** * Warning: inherently not thread safe. There may be changes to the model which render the * returned index obsolete before you get a chance to use it. * * @param item the object to be found in the list * @return the position where the item was found, or -1 if not found */ @CallSuper @NonNull @CheckResult(suggest = IAltFuture.CHECK_RESULT_SUGGESTION) public IAltFuture<?, Integer> getPositionAsync(@NonNull T item) { return new RunnableAltFuture<>(UI, () -> getPosition(item)); } @CallSuper @NonNull public Filter getFilter() { return super.getFilter(); } @CallSuper @NonNull @CheckResult(suggest = IAltFuture.CHECK_RESULT_SUGGESTION) public IAltFuture<?, Filter> getFilterAsync() { return new RunnableAltFuture<>(UI, this::getFilter); } @CallSuper public long getItemId(@IntRange(from = 0, to = Integer.MAX_VALUE) final int position) { return super.getItemId(position); } /** * Warning: inherently not thread safe. There may be changes to the model which render the * returned index obsolete before you get a chance to use it. * * @param position * @return */ @CallSuper @NonNull @CheckResult(suggest = IAltFuture.CHECK_RESULT_SUGGESTION) public IAltFuture<?, Long> getItemIdAsync(@IntRange(from = 0, to = Integer.MAX_VALUE) int position) { return new RunnableAltFuture<>(UI, () -> getItemId(position)); } @CallSuper @Override public void setDropDownViewResource(@LayoutRes int resource) { super.setDropDownViewResource(resource); } @CallSuper @NonNull @CheckResult(suggest = IAltFuture.CHECK_RESULT_SUGGESTION) public <A> IAltFuture<A, A> setDropDownViewResourceAsync(@LayoutRes int resource) { return new RunnableAltFuture<>(UI, () -> setDropDownViewResource(resource)); } @CallSuper @NonNull @Override public View getView(@IntRange(from = 0, to = Integer.MAX_VALUE) int position, @Nullable View convertView, @NonNull ViewGroup parent) { return super.getView(position, convertView, parent); } /** * Warning: inherently not thread safe. There may be changes to the model which render the * returned index obsolete before you get a chance to use it. * * @param position * @param convertView * @param parent * @param <A> * @return */ @CallSuper @NonNull @CheckResult(suggest = IAltFuture.CHECK_RESULT_SUGGESTION) public <A> IAltFuture<A, View> getViewAsync(@IntRange(from = 0, to = Integer.MAX_VALUE) int position, @NonNull View convertView, @NonNull ViewGroup parent) { return new RunnableAltFuture<>(UI, () -> getView(position, convertView, parent)); } @CallSuper @NonNull @Override public View getDropDownView(@IntRange(from = 0, to = Integer.MAX_VALUE) int position, @NonNull View convertView, @NonNull ViewGroup parent) { return super.getDropDownView(position, convertView, parent); } @CallSuper @NonNull @CheckResult(suggest = IAltFuture.CHECK_RESULT_SUGGESTION) public IAltFuture<?, View> getDropDownViewAsync(@IntRange(from = 0, to = Integer.MAX_VALUE) int position, @NonNull View convertView, @NonNull ViewGroup parent) { return new RunnableAltFuture<>(UI, () -> getDropDownView(position, convertView, parent)); } @CallSuper @NonNull @CheckResult(suggest = IAltFuture.CHECK_RESULT_SUGGESTION) public IAltFuture<?, Boolean> isEmptyAsync() { return new RunnableAltFuture<>(UI, this::isEmpty); } @NonNull @Override public ImmutableValue<String> getOrigin() { return mOrigin; } }