delegates refactored a bit

This commit is contained in:
Gavriil Sitnikov 2017-05-12 19:30:19 +03:00
commit 14ec33956f
4 changed files with 302 additions and 116 deletions

View File

@ -3,8 +3,6 @@ package ru.touchin.roboswag.components.adapters;
import android.support.annotation.NonNull;
import android.view.ViewGroup;
import java.util.List;
import ru.touchin.roboswag.components.utils.LifecycleBindable;
import ru.touchin.roboswag.components.utils.UiUtils;
import rx.Completable;
@ -18,12 +16,11 @@ import rx.functions.Action1;
* Objects of such class controls creation and binding of specific type of RecyclerView's ViewHolders.
* Default {@link #getItemViewType} is generating on construction of object.
*
* @param <TViewHolder> Type of {@link BindableViewHolder} of delegate;
* @param <TItem> Type of items to bind to {@link BindableViewHolder}s.
* @param <TViewHolder> Type of {@link BindableViewHolder} of delegate.
*/
@SuppressWarnings("PMD.TooManyMethods")
//TooManyMethods: it's ok as it is LifecycleBindable
public abstract class AdapterDelegate<TViewHolder extends BindableViewHolder, TItem> implements LifecycleBindable {
//TooManyMethods: it's ok
public abstract class AdapterDelegate<TViewHolder extends BindableViewHolder> implements LifecycleBindable {
@NonNull
private final LifecycleBindable parentLifecycleBindable;
@ -53,29 +50,6 @@ public abstract class AdapterDelegate<TViewHolder extends BindableViewHolder, TI
return defaultItemViewType;
}
/**
* Returns if object is processable by this delegate.
* This item will be casted to {@link TItem} and passes to {@link #onBindViewHolder(TViewHolder, TItem, int, int)}.
*
* @param item Item to check;
* @param adapterPosition Position of item in adapter;
* @param itemCollectionPosition Position of item in collection that contains item;
* @return True if item is processable by this delegate.
*/
public abstract boolean isForViewType(@NonNull final Object item, final int adapterPosition, final int itemCollectionPosition);
/**
* Returns unique ID of item to support stable ID's logic of RecyclerView's adapter.
*
* @param item Item to check;
* @param adapterPosition Position of item in adapter;
* @param itemCollectionPosition Position of item in collection that contains item;
* @return Unique item ID.
*/
public long getItemId(@NonNull final TItem item, final int adapterPosition, final int itemCollectionPosition) {
return 0;
}
/**
* Creates ViewHolder to bind item to it later.
*
@ -85,31 +59,6 @@ public abstract class AdapterDelegate<TViewHolder extends BindableViewHolder, TI
@NonNull
public abstract TViewHolder onCreateViewHolder(@NonNull final ViewGroup parent);
/**
* Binds item to created by this object ViewHolder.
*
* @param holder ViewHolder to bind item to;
* @param item Item to check;
* @param adapterPosition Position of item in adapter;
* @param itemCollectionPosition Position of item in collection that contains item;
*/
public abstract void onBindViewHolder(@NonNull final TViewHolder holder, @NonNull final TItem item,
final int adapterPosition, final int itemCollectionPosition);
/**
* Binds item with payloads to created by this object ViewHolder.
*
* @param holder ViewHolder to bind item to;
* @param item Item to check;
* @param payloads Payloads;
* @param adapterPosition Position of item in adapter;
* @param itemCollectionPosition Position of item in collection that contains item;
*/
public void onBindViewHolder(@NonNull final TViewHolder holder, @NonNull final TItem item, @NonNull final List<Object> payloads,
final int adapterPosition, final int itemCollectionPosition) {
//do nothing by default
}
@SuppressWarnings("CPD-START")
//CPD: it is same as in other implementation based on BaseLifecycleBindable
@NonNull

View File

@ -0,0 +1,81 @@
package ru.touchin.roboswag.components.adapters;
import android.support.annotation.NonNull;
import android.view.ViewGroup;
import java.util.List;
import ru.touchin.roboswag.components.utils.LifecycleBindable;
/**
* Objects of such class controls creation and binding of specific type of RecyclerView's ViewHolders.
* Such delegates are creating and binding ViewHolders for specific items.
* Default {@link #getItemViewType} is generating on construction of object.
*
* @param <TViewHolder> Type of {@link BindableViewHolder} of delegate;
* @param <TItem> Type of items to bind to {@link BindableViewHolder}s.
*/
public abstract class ItemAdapterDelegate<TViewHolder extends BindableViewHolder, TItem> extends AdapterDelegate<TViewHolder> {
public ItemAdapterDelegate(@NonNull final LifecycleBindable parentLifecycleBindable) {
super(parentLifecycleBindable);
}
/**
* Returns if object is processable by this delegate.
* This item will be casted to {@link TItem} and passes to {@link #onBindViewHolder(TViewHolder, TItem, int, int)}.
*
* @param item Item to check;
* @param positionInAdapter Position of item in adapter;
* @param itemCollectionPosition Position of item in collection that contains item;
* @return True if item is processable by this delegate.
*/
public abstract boolean isForViewType(@NonNull final Object item, final int positionInAdapter, final int itemCollectionPosition);
/**
* Returns unique ID of item to support stable ID's logic of RecyclerView's adapter.
*
* @param item Item to check;
* @param positionInAdapter Position of item in adapter;
* @param positionInCollection Position of item in collection that contains item;
* @return Unique item ID.
*/
public long getItemId(@NonNull final TItem item, final int positionInAdapter, final int positionInCollection) {
return 0;
}
/**
* Creates ViewHolder to bind item to it later.
*
* @param parent Container of ViewHolder's view.
* @return New ViewHolder.
*/
@NonNull
public abstract TViewHolder onCreateViewHolder(@NonNull final ViewGroup parent);
/**
* Binds item to created by this object ViewHolder.
*
* @param holder ViewHolder to bind item to;
* @param item Item to check;
* @param positionInAdapter Position of item in adapter;
* @param positionInCollection Position of item in collection that contains item;
*/
public abstract void onBindViewHolder(@NonNull final TViewHolder holder, @NonNull final TItem item,
final int positionInAdapter, final int positionInCollection);
/**
* Binds item with payloads to created by this object ViewHolder.
*
* @param holder ViewHolder to bind item to;
* @param item Item to check;
* @param payloads Payloads;
* @param positionInAdapter Position of item in adapter;
* @param positionInCollection Position of item in collection that contains item;
*/
public void onBindViewHolder(@NonNull final TViewHolder holder, @NonNull final TItem item, @NonNull final List<Object> payloads,
final int positionInAdapter, final int positionInCollection) {
//do nothing by default
}
}

View File

@ -42,6 +42,9 @@ import ru.touchin.roboswag.core.utils.Optional;
import ru.touchin.roboswag.core.utils.ShouldNotHappenException;
import rx.Observable;
import rx.android.schedulers.AndroidSchedulers;
import rx.functions.Action1;
import rx.functions.Action2;
import rx.functions.Action3;
import rx.subjects.BehaviorSubject;
/**
@ -56,12 +59,22 @@ import rx.subjects.BehaviorSubject;
* @param <TItem> Type of items to bind to ViewHolders;
* @param <TItemViewHolder> Type of ViewHolders to show items.
*/
@SuppressWarnings("unchecked")
@SuppressWarnings({"unchecked", "PMD.TooManyMethods"})
//TooManyMethods: it's ok
public abstract class ObservableCollectionAdapter<TItem, TItemViewHolder extends BindableViewHolder>
extends RecyclerView.Adapter<BindableViewHolder> {
private static final int PRE_LOADING_COUNT = 20;
private static boolean inDebugMode;
/**
* Enables debugging features like checking concurrent delegates.
*/
public static void setInDebugMode() {
inDebugMode = true;
}
@NonNull
private final BehaviorSubject<Optional<ObservableCollection<TItem>>> observableCollectionSubject
= BehaviorSubject.create(new Optional<>(null));
@ -80,7 +93,7 @@ public abstract class ObservableCollectionAdapter<TItem, TItemViewHolder extends
@NonNull
private final List<RecyclerView> attachedRecyclerViews = new LinkedList<>();
@NonNull
private final List<AdapterDelegate<TItemViewHolder, TItem>> delegates = new ArrayList<>();
private final List<AdapterDelegate<? extends BindableViewHolder>> delegates = new ArrayList<>();
public ObservableCollectionAdapter(@NonNull final LifecycleBindable lifecycleBindable) {
super();
@ -301,23 +314,38 @@ public abstract class ObservableCollectionAdapter<TItem, TItemViewHolder extends
* @return List of {@link AdapterDelegate}.
*/
@NonNull
public List<AdapterDelegate<TItemViewHolder, TItem>> getDelegates() {
public List<AdapterDelegate<? extends BindableViewHolder>> getDelegates() {
return Collections.unmodifiableList(delegates);
}
/**
* Adds {@link AdapterDelegate} to adapter.
* Adds {@link ItemAdapterDelegate} to adapter.
*
* @param delegate Delegate to add.
*/
public void addDelegate(@NonNull final AdapterDelegate<? extends TItemViewHolder, ? extends TItem> delegate) {
for (final AdapterDelegate addedDelegate : delegates) {
if (addedDelegate.getItemViewType() == delegate.getItemViewType()) {
Lc.assertion("AdapterDelegate with viewType=" + delegate.getItemViewType() + " already added");
return;
public void addDelegate(@NonNull final ItemAdapterDelegate<? extends TItemViewHolder, ? extends TItem> delegate) {
addDelegateInternal(delegate);
}
/**
* Adds {@link PositionAdapterDelegate} to adapter.
*
* @param delegate Delegate to add.
*/
public void addDelegate(@NonNull final PositionAdapterDelegate<? extends BindableViewHolder> delegate) {
addDelegateInternal(delegate);
}
private void addDelegateInternal(@NonNull final AdapterDelegate<? extends BindableViewHolder> delegate) {
if (inDebugMode) {
for (final AdapterDelegate addedDelegate : delegates) {
if (addedDelegate.getItemViewType() == delegate.getItemViewType()) {
Lc.assertion("AdapterDelegate with viewType=" + delegate.getItemViewType() + " already added");
return;
}
}
}
delegates.add((AdapterDelegate<TItemViewHolder, TItem>) delegate);
delegates.add(delegate);
notifyDataSetChanged();
}
@ -326,41 +354,88 @@ public abstract class ObservableCollectionAdapter<TItem, TItemViewHolder extends
*
* @param delegate Delegate to remove.
*/
public void removeDelegate(@NonNull final AdapterDelegate<? extends TItemViewHolder, ? extends TItem> delegate) {
delegates.remove((AdapterDelegate<TItemViewHolder, TItem>) delegate);
public void removeDelegate(@NonNull final AdapterDelegate<? extends BindableViewHolder> delegate) {
delegates.remove(delegate);
notifyDataSetChanged();
}
private void checkDelegates(@Nullable final AdapterDelegate alreadyPickedDelegate, @NonNull final AdapterDelegate currentDelegate) {
if (alreadyPickedDelegate != null) {
throw new ShouldNotHappenException("Concurrent delegates: " + currentDelegate + " and " + alreadyPickedDelegate);
}
}
private int getItemPositionInCollection(final int positionInAdapter) {
final int shiftedPosition = positionInAdapter - getHeadersCount();
return shiftedPosition >= 0 && shiftedPosition < innerCollection.size() ? shiftedPosition : -1;
}
@SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.ModifiedCyclomaticComplexity", "PMD.StdCyclomaticComplexity", "PMD.NPathComplexity"})
//Complexity: because of debug code
@Override
public int getItemViewType(final int positionInAdapter) {
final int positionInCollection = positionInAdapter - getHeadersCount();
if (positionInCollection < 0 || positionInCollection >= innerCollection.size()) {
return super.getItemViewType(positionInAdapter);
}
final TItem item = innerCollection.get(positionInCollection);
for (final AdapterDelegate<?, TItem> delegate : delegates) {
if (delegate.isForViewType(item, positionInAdapter, positionInCollection)) {
return delegate.getItemViewType();
AdapterDelegate delegateOfViewType = null;
final int positionInCollection = getItemPositionInCollection(positionInAdapter);
final TItem item = positionInCollection >= 0 ? innerCollection.get(positionInCollection) : null;
for (final AdapterDelegate<?> delegate : delegates) {
if (delegate instanceof ItemAdapterDelegate) {
if (item != null && ((ItemAdapterDelegate) delegate).isForViewType(item, positionInAdapter, positionInCollection)) {
checkDelegates(delegateOfViewType, delegate);
delegateOfViewType = delegate;
if (!inDebugMode) {
break;
}
}
} else if (delegate instanceof PositionAdapterDelegate) {
if (((PositionAdapterDelegate) delegate).isForViewType(positionInAdapter)) {
checkDelegates(delegateOfViewType, delegate);
delegateOfViewType = delegate;
if (!inDebugMode) {
break;
}
}
} else {
Lc.assertion("Delegate of type " + delegate.getClass());
}
}
return super.getItemViewType(positionInAdapter);
return delegateOfViewType != null ? delegateOfViewType.getItemViewType() : super.getItemViewType(positionInAdapter);
}
@Override
public long getItemId(final int positionInAdapter) {
final int positionInCollection = positionInAdapter - getHeadersCount();
if (positionInCollection < 0 || positionInCollection >= innerCollection.size()) {
return super.getItemId(positionInAdapter);
}
final LongContainer result = new LongContainer();
tryDelegateAction(positionInAdapter,
(itemAdapterDelegate, item, positionInCollection) ->
result.value = itemAdapterDelegate.getItemId(item, positionInAdapter, positionInCollection),
positionAdapterDelegate -> result.value = positionAdapterDelegate.getItemId(positionInAdapter),
(item, positionInCollection) -> result.value = super.getItemId(positionInAdapter));
return result.value;
}
final int itemViewType = getItemViewType(positionInAdapter);
final TItem item = innerCollection.get(positionInCollection);
for (final AdapterDelegate<?, TItem> delegate : delegates) {
if (delegate.getItemViewType() == itemViewType) {
return delegate.getItemId(item, positionInAdapter, positionInCollection);
private void tryDelegateAction(final int positionInAdapter,
@NonNull final Action3<ItemAdapterDelegate, TItem, Integer> itemAdapterDelegateAction,
@NonNull final Action1<PositionAdapterDelegate> positionAdapterDelegateAction,
@NonNull final Action2<TItem, Integer> defaultAction) {
final int viewType = getItemViewType(positionInAdapter);
final int positionInCollection = getItemPositionInCollection(positionInAdapter);
final TItem item = positionInCollection >= 0 ? innerCollection.get(positionInCollection) : null;
for (final AdapterDelegate<?> delegate : delegates) {
if (delegate instanceof ItemAdapterDelegate) {
if (item != null && viewType == delegate.getItemViewType()) {
itemAdapterDelegateAction.call((ItemAdapterDelegate) delegate, item, positionInCollection);
return;
}
} else if (delegate instanceof PositionAdapterDelegate) {
if (viewType == delegate.getItemViewType()) {
positionAdapterDelegateAction.call((PositionAdapterDelegate) delegate);
return;
}
} else {
Lc.assertion("Delegate of type " + delegate.getClass());
}
}
return super.getItemId(positionInAdapter);
defaultAction.call(item, positionInCollection);
}
@Override
@ -371,7 +446,7 @@ public abstract class ObservableCollectionAdapter<TItem, TItemViewHolder extends
@NonNull
@Override
public BindableViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
for (final AdapterDelegate<?, TItem> delegate : delegates) {
for (final AdapterDelegate<?> delegate : delegates) {
if (delegate.getItemViewType() == viewType) {
return delegate.onCreateViewHolder(parent);
}
@ -383,26 +458,37 @@ public abstract class ObservableCollectionAdapter<TItem, TItemViewHolder extends
public void onBindViewHolder(@NonNull final BindableViewHolder holder, final int positionInAdapter) {
lastUpdatedChangeNumber = innerCollection.getChangesCount();
final int positionInCollection = positionInAdapter - getHeadersCount();
if (positionInCollection < 0 || positionInCollection >= innerCollection.size()) {
return;
}
updateMoreAutoLoadingRequest(positionInCollection);
bindItemViewHolder(holder, null, positionInAdapter, positionInCollection);
tryDelegateAction(positionInAdapter,
(itemAdapterDelegate, item, positionInCollection) -> {
bindItemViewHolder(itemAdapterDelegate, holder, item, null, positionInAdapter, positionInCollection);
updateMoreAutoLoadingRequest(positionInCollection);
},
positionAdapterDelegate -> positionAdapterDelegate.onBindViewHolder(holder, positionInAdapter),
(item, positionInCollection) -> {
if (item != null) {
bindItemViewHolder(null, holder, item, null, positionInAdapter, positionInCollection);
}
});
}
@Override
public void onBindViewHolder(@NonNull final BindableViewHolder holder, final int positionInAdapter, @NonNull final List<Object> payloads) {
super.onBindViewHolder(holder, positionInAdapter, payloads);
final int positionInCollection = positionInAdapter - getHeadersCount();
if (positionInCollection < 0 || positionInCollection >= innerCollection.size()) {
return;
}
bindItemViewHolder(holder, payloads, positionInAdapter, positionInCollection);
tryDelegateAction(positionInAdapter,
(itemAdapterDelegate, item, positionInCollection) -> {
bindItemViewHolder(itemAdapterDelegate, holder, item, payloads, positionInAdapter, positionInCollection);
updateMoreAutoLoadingRequest(positionInCollection);
},
positionAdapterDelegate -> positionAdapterDelegate.onBindViewHolder(holder, positionInAdapter),
(item, positionInCollection) -> {
if (item != null) {
bindItemViewHolder(null, holder, item, payloads, positionInAdapter, positionInCollection);
}
});
}
private void bindItemViewHolder(@NonNull final BindableViewHolder holder, @Nullable final List<Object> payloads,
private void bindItemViewHolder(@Nullable final ItemAdapterDelegate<TItemViewHolder, TItem> itemAdapterDelegate,
@NonNull final BindableViewHolder holder, @NonNull final TItem item, @Nullable final List<Object> payloads,
final int positionInAdapter, final int positionInCollection) {
final TItemViewHolder itemViewHolder;
try {
@ -411,24 +497,19 @@ public abstract class ObservableCollectionAdapter<TItem, TItemViewHolder extends
Lc.assertion(exception);
return;
}
final TItem item = innerCollection.get(positionInCollection);
final int itemViewType = getItemViewType(positionInAdapter);
updateClickListener(holder, item, positionInAdapter, positionInCollection);
for (final AdapterDelegate<TItemViewHolder, TItem> delegate : delegates) {
if (itemViewType == delegate.getItemViewType()) {
if (payloads == null) {
delegate.onBindViewHolder(itemViewHolder, item, positionInAdapter, positionInCollection);
} else {
delegate.onBindViewHolder(itemViewHolder, item, payloads, positionInAdapter, positionInCollection);
}
return;
if (itemAdapterDelegate != null) {
if (payloads == null) {
itemAdapterDelegate.onBindViewHolder(itemViewHolder, item, positionInAdapter, positionInCollection);
} else {
itemAdapterDelegate.onBindViewHolder(itemViewHolder, item, payloads, positionInAdapter, positionInCollection);
}
}
if (payloads == null) {
onBindItemToViewHolder(itemViewHolder, positionInAdapter, item);
} else {
onBindItemToViewHolder(itemViewHolder, positionInAdapter, item, payloads);
if (payloads == null) {
onBindItemToViewHolder(itemViewHolder, positionInAdapter, item);
} else {
onBindItemToViewHolder(itemViewHolder, positionInAdapter, item, payloads);
}
}
}
@ -485,8 +566,8 @@ public abstract class ObservableCollectionAdapter<TItem, TItemViewHolder extends
@Nullable
public TItem getItem(final int positionInAdapter) {
final int positionInCollection = positionInAdapter - getHeadersCount();
return positionInCollection < 0 || positionInCollection >= innerCollection.size() ? null : innerCollection.get(positionInCollection);
final int positionInCollection = getItemPositionInCollection(positionInAdapter);
return positionInCollection >= 0 ? innerCollection.get(positionInCollection) : null;
}
/**
@ -577,4 +658,10 @@ public abstract class ObservableCollectionAdapter<TItem, TItemViewHolder extends
}
private class LongContainer {
private long value;
}
}

View File

@ -0,0 +1,69 @@
package ru.touchin.roboswag.components.adapters;
import android.support.annotation.NonNull;
import android.view.ViewGroup;
import java.util.List;
import ru.touchin.roboswag.components.utils.LifecycleBindable;
/**
* Objects of such class controls creation and binding of specific type of RecyclerView's ViewHolders.
* Such delegates are creating and binding ViewHolders by position in adapter.
* Default {@link #getItemViewType} is generating on construction of object.
*
* @param <TViewHolder> Type of {@link BindableViewHolder} of delegate.
*/
public abstract class PositionAdapterDelegate<TViewHolder extends BindableViewHolder> extends AdapterDelegate<TViewHolder> {
public PositionAdapterDelegate(@NonNull final LifecycleBindable parentLifecycleBindable) {
super(parentLifecycleBindable);
}
/**
* Returns if object is processable by this delegate.
*
* @param positionInAdapter Position of item in adapter;
* @return True if item is processable by this delegate.
*/
public abstract boolean isForViewType(final int positionInAdapter);
/**
* Returns unique ID of item to support stable ID's logic of RecyclerView's adapter.
*
* @param positionInAdapter Position of item in adapter;
* @return Unique item ID.
*/
public long getItemId(final int positionInAdapter) {
return 0;
}
/**
* Creates ViewHolder to bind position to it later.
*
* @param parent Container of ViewHolder's view.
* @return New ViewHolder.
*/
@NonNull
public abstract TViewHolder onCreateViewHolder(@NonNull final ViewGroup parent);
/**
* Binds position to ViewHolder.
*
* @param holder ViewHolder to bind position to;
* @param positionInAdapter Position of item in adapter.
*/
public abstract void onBindViewHolder(@NonNull final TViewHolder holder, final int positionInAdapter);
/**
* Binds position with payloads to ViewHolder.
*
* @param holder ViewHolder to bind position to;
* @param payloads Payloads;
* @param positionInAdapter Position of item in adapter.
*/
public void onBindViewHolder(@NonNull final TViewHolder holder, @NonNull final List<Object> payloads, final int positionInAdapter) {
//do nothing by default
}
}