RxJava2 función de rebote no funciona correctamente en RecyclerView – Android

Estoy intentando crear un ImageButton de encargo que acumule los tecleos y dispare un acontecimiento cuando el usuario deja de hacer clic en el botón por 1 segundo.

He utilizado la función de rebote para lograr esto.

El ImageButton personalizado:

public class MBImageButton extends ImageButton { private AtomicInteger mCounter; private Disposable mDisposable; private Observable<Object> observable; private OnAccumulatedRequestsRead mOnAccumulatedRequestsRead; private OnEverClickListener mOnEverClickListener; private int emitEveryMilli = 1000; // every 1 second by default private boolean shouldDisposeOnDetachFromWindow = true; public MBImageButton(Context context) { super(context); init(); } public MBImageButton(Context context, AttributeSet attrs) { super(context, attrs); init(); } public MBImageButton(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } public void setAccumulatedClickListeners(OnEverClickListener onEverClickListener, OnAccumulatedRequestsRead onAccumulatedRequestsRead) { setOnAccumulatedRequestsRead(onAccumulatedRequestsRead); setOnEverClickListener(onEverClickListener); initClickObservable(); subscribe(); } private void initClickObservable() { observable = Observable.create(emitter -> { emitter.setCancellable(() -> setOnClickListener(null)); try { setOnClickListener(view -> { try { final int currentCount = mCounter.incrementAndGet(); Timber.d("Clicked: " + currentCount); if (mOnEverClickListener != null) { mOnEverClickListener.onEveryClickListener(currentCount); } emitter.onNext(new Object()); } catch (Exception e) { emitter.onError(e); } }); } catch (Exception e) { emitter.onError(e); } }).doOnSubscribe(disposable -> mDisposable = disposable) .debounce(emitEveryMilli, TimeUnit.MILLISECONDS); } private void subscribe() { observable.subscribeOn(Schedulers.newThread()).observeOn(AndroidSchedulers.mainThread()) .subscribe(o -> { try { final int count = mCounter.get(); if(count==0) return; mCounter.set(0); Timber.d("Accumulated Clicks: " + count); if (mOnAccumulatedRequestsRead != null) { mOnAccumulatedRequestsRead.onAccumulatedRequestsReady(count); } } catch (Exception e) { Timber.e(e); } }, Timber::e); } private void init() { mCounter = new AtomicInteger(0); } public void disposeAccumulatedClickListeners() { if (mDisposable != null) { mDisposable.dispose(); } } public void shouldDisposeOnDetachFromWindow(boolean shouldDisposeOnDetachFromWindow) { this.shouldDisposeOnDetachFromWindow = shouldDisposeOnDetachFromWindow; } public void setEmitEveryMilliseconds(int emitEveryMilli) { this.emitEveryMilli = emitEveryMilli; initClickObservable(); subscribe(); } private void setOnEverClickListener(OnEverClickListener onEverClickListener) { mOnEverClickListener = onEverClickListener; } private void setOnAccumulatedRequestsRead(OnAccumulatedRequestsRead onAccumulatedRequestsRead) { mOnAccumulatedRequestsRead = onAccumulatedRequestsRead; } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); if (shouldDisposeOnDetachFromWindow) { if (mDisposable != null) { mDisposable.dispose(); } } } public interface OnAccumulatedRequestsRead { void onAccumulatedRequestsReady(int count); } public interface OnEverClickListener { void onEveryClickListener(int currentCount); } } 

La función que configura esta imageButton para debouncing es:

 setAccumulatedClickListeners(OnEverClickListener, OnAccumulatedRequestsRead) 

Cuando configuro esta vista desde RecyclerView , todo funciona correctamente,

El resultado se ve así cuando haga clic en 9 veces secuencialmente:

 D/MBImageButton: Clicked: 1 D/MBImageButton: Clicked: 2 D/MBImageButton: Clicked: 3 D/MBImageButton: Clicked: 4 D/MBImageButton: Clicked: 5 D/MBImageButton: Clicked: 6 D/MBImageButton: Clicked: 7 D/MBImageButton: Clicked: 8 D/MBImageButton: Clicked: 9 D/MBImageButton: Accumulated Clicks: 9 

Cuando agrego este ImageButton a RecyclerView ViewHolder el resultado no es correcto para el mismo número de clics:

 D/MBImageButton: Clicked: 1 D/MBImageButton: Clicked: 2 D/MBImageButton: Clicked: 1 D/MBImageButton: Clicked: 3 D/MBImageButton: Clicked: 2 D/MBImageButton: Clicked: 4 D/MBImageButton: Accumulated Clicks: 4 D/MBImageButton: Clicked: 1 D/MBImageButton: Accumulated Clicks: 2 D/MBImageButton: Clicked: 2 D/MBImageButton: Accumulated Clicks: 2 D/MBImageButton: Accumulated Clicks: 0 D/MBImageButton: Clicked: 1 D/MBImageButton: Accumulated Clicks: 1 D/MBImageButton: Accumulated Clicks: 0 D/MBImageButton: Accumulated Clicks: 0 

Este es otro resultado para nuevamente 9 clics secuenciales:

 D/MBImageButton: Clicked: 1 D/MBImageButton: Clicked: 2 D/MBImageButton: Clicked: 1 D/MBImageButton: Clicked: 3 D/MBImageButton: Clicked: 2 D/MBImageButton: Clicked: 4 D/MBImageButton: Clicked: 3 D/MBImageButton: Accumulated Clicks: 4 D/MBImageButton: Clicked: 1 D/MBImageButton: Accumulated Clicks: 3 D/MBImageButton: Accumulated Clicks: 1 D/MBImageButton: Accumulated Clicks: 0 D/MBImageButton: Clicked: 1 D/MBImageButton: Accumulated Clicks: 1 D/MBImageButton: Accumulated Clicks: 0 D/MBImageButton: Accumulated Clicks: 0 D/MBImageButton: Accumulated Clicks: 0 

En ambas veces hice clic con el mismo intervalo de tiempo, pero cuando este botón agregado a RecyclerView da extraño resultado.

Cual puede ser el problema ?

Actualizar:

Mi adaptador simplificó la implementación:

 public class ProductRVAdapter extends BaseProductRVAdapter<ProductRVAdapter.ProductVH> { private ProductAdapterListener mProductAdapterListener; public ProductRVAdapter(List<Product> productList, ProductAdapterListener productAdapterListener) { super(productList); mProductAdapterListener = productAdapterListener; } @Override public ProductVH onCreateViewHolder(ViewGroup parent, int viewType) { final View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_product, parent, false); return new ProductVH(view); } @Override public void onBindViewHolder(ProductVH holder, int position) { holder.bind(productList.get(position), position); holder.ib_decrease.setAccumulatedClickListeners(currentCount -> onProductAdapterDecreaseClicked.onClick(holder.ib_decrease), count -> { if (mProductAdapterListener != null) { mProductAdapterListener.onProductAdapterProductChangeToWSListener(productList.get(position), position, -count); } }); holder.ib_add.setAccumulatedClickListeners(currentCount -> onProductAdapterAddToCartClicked.onClick(holder.ib_add), count -> { if (mProductAdapterListener != null) { mProductAdapterListener.onProductAdapterProductChangeToWSListener(productList.get(position), position, count); } }); } public interface ProductAdapterListener { void onProductAdapterAddToCartClicked(Product product, int position); void onProductAdapterProductChangeToWSListener(Product product, int position, int amountChanged); void onProductAdapterDecreaseClicked(Product product, int position); } public static class ProductVH extends RecyclerView.ViewHolder { @BindView(R.id.iv_productImage) ImageView iv_producImage; @BindView(R.id.tv_productName) TextView tv_productName; @BindView(R.id.tv_prodAmount) MBTextView tv_prodAmount; @BindView(R.id.ib_decrease) MBImageButton ib_decrease; @BindView(R.id.ib_add) MBImageButton ib_add; ProductVH(View itemView) { super(itemView); ButterKnife.bind(this, itemView); } public void bind(Product product, int position) { tv_productName.setText(product.getName()); tv_prodAmount.setText(product.getAmount()); ImageLoader.loadImage(iv_producImage.getContext(), iv_producImage, product.getImg(), R.drawable.ic_category_item); ib_decrease.setTag(position); ib_add.setTag(position); } } } 

Conseguí la solución

El problema principal es cuando hago scroll en el RecyclerView Adapter , los vies se separan de la ventana y la función onDetachedFromWindow se llama, en esa función i unsubscribe (dispose) de la observable.

Las soluciones intentaron:

1. Añadí attachFunction

 @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); if (shouldDisposeOnDetachFromWindow) { if (mDisposable != null) { if (mDisposable.isDisposed()) { initClickObservable(); subscribe(); } } } } 

2. Se eliminaron las llamadas notifyItemChanged (posición)

Normalmente actualizo la vista después de alguna modificación usando notifyItemChange() . Ahora actualizo la vista directamente por setText o funciones similares.

Funciona correctamente ahora.

  • Hacer sucesivas llamadas de api con Retrofit y devolver observable
  • Java.lang.NoClassDefFoundError: io.reactivex.Observable
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.