Как обрабатывать клики по элементам для просмотра переработчика с помощью RxJava

Мне было интересно узнать, как лучше всего реагировать на щелчок элемента в представлении переработчика.

Обычно я добавляю прослушиватель onclick() в ViewHolder и возвращаю результаты активности/фрагменту через интерфейс.

Я думал о добавлении Observable в onBindViewHolder, но я не хочу создавать новый Observable для каждой привязки элемента.


person J Whitfield    schedule 08.04.2016    source источник
comment
Вы можете использовать PublishSubject. Тогда вы создаете его только один раз и просто выдаете на него данные, при этом подписавшись только один раз.   -  person Pavlos    schedule 08.04.2016


Ответы (5)


Вы можете использовать RxBinding, а затем создать тему внутри вашего адаптера, а затем перенаправить все события на эту тему и просто создайте геттер субъекта, который будет действовать как наблюдаемый, и, наконец, просто подпишитесь на этот наблюдаемый.

private PublishSubject<View> mViewClickSubject = PublishSubject.create();

public Observable<View> getViewClickedObservable() {
    return mViewClickSubject.asObservable();
}

@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup pParent, int pViewType) {
    Context context = pParent.getContext();
    View view = (View) LayoutInflater.from(context).inflate(R.layout.your_item_layout, pParent, false);
    ViewHolder viewHolder = new ViewHolder(view);

    RxView.clicks(view)
            .takeUntil(RxView.detaches(pParent))
            .map(aVoid -> view)
            .subscribe(mViewClickSubject);

    return viewHolder;
}

Пример использования может быть:

mMyAdapter.getViewClickedObservable()
        .subscribe(view -> /* do the action. */);
person epool    schedule 10.10.2016
comment
Где вы отписываетесь? - person EpicPandaForce; 10.10.2016
comment
Yo может справиться с этим, когда вы делаете подписку. Возможно, вы используете CompositeSubscription, если у вас есть более одной подписки. - person epool; 10.10.2016
comment
если вам нужно только наблюдать (в общем случае!) - вам нужно вызвать mViewClickSubject.asObservable(). иначе каждый подписчик может вызвать onErro()(например, что не нормально! - person Andriy Antonov; 08.02.2017
comment
@epool Какая польза от .takeUntil(RxView.detaches(pParent))? В любом случае, как только представление будет переработано, кликов не будет, поэтому нужно ли нам останавливать поток кликов после того, как представление будет отсоединено от родителя? - person Ruwanka Madhushan; 28.06.2017
comment
@RuwankaMadhushan, скорее всего, для того, чтобы отписаться, когда просмотр отключен. Это происходит, когда элемент прокручивается за пределы экрана. - person Odys; 04.07.2017
comment
@ Одис Это правда. Но у меня проблема с использованием этого подхода во фрагментах. И я не мог заставить эту работу takeUntil() в этом случае, как в здесь. Достаточно ли позвонить onCompleted на onDetachedFromRecyclerView? - person Ruwanka Madhushan; 05.07.2017

Шаг 1. Перенесите бизнес-логику из действий в классы/службы предметной области.

Необязательно: используйте https://github.com/roboguice/roboguice, чтобы легко связать свои сервисы друг с другом. .

Шаг 2: @Inject (или просто установите) ваш сервис в адаптер

Шаг 3: Возьмите https://github.com/JakeWharton/RxBinding и используйте сверхспособности вашего адаптера:

RxView.clicks(button).subscribe(new Action1<Void>() {
    @Override
    public void call(Void aVoid) {
        myCoolService.doStuff();
    }
});

Шаг 4. Получите сбой во время выполнения и узнайте, как справляться с подписками

Шаг 5: ПРИБЫЛЬ :)

person Entea    schedule 18.04.2016

Я бы посоветовал вам сделать с вашим первоначальным подходом наблюдаемого для каждого элемента при щелчке, но чтобы не создавать новый наблюдаемый каждый раз, когда вы можете просто кэшировать элементы, испускаемые в первый раз, используя кеш.

      /**
 * Here we can prove how the first time the items are delayed 100 ms per item emitted but second time becuase it´s cached we dont have any delay since 
 * the item emitted are cached
 */
@Test
public void cacheObservable() {
    Integer[] numbers = {0, 1, 2, 3, 4, 5};

    Observable<Integer> observable = Observable.from(numbers)
                                               .doOnNext(number -> {
                                                   try {
                                                       Thread.sleep(100);
                                                   } catch (InterruptedException e) {
                                                       e.printStackTrace();
                                                   }
                                               })
                                               .cache();
    long time = System.currentTimeMillis();
    observable.subscribe(System.out::println);
    System.out.println("First time took:" + (System.currentTimeMillis() - time));
    time = System.currentTimeMillis();
    observable.subscribe(System.out::println);
    System.out.println("Second time took:" + (System.currentTimeMillis() - time));

}
person paul    schedule 19.04.2016

Мое решение было очень похоже на решение @epool, за исключением использования модели EventBus.

Сначала создайте RxBus: RxBus.java

public class RxBus {
    private final Subject<Object, Object> _bus = new SerializedSubject<>(PublishSubject.create());
    public void send(Object o) { _bus.onNext(o); }
    public Observable<Object> toObserverable() { return _bus; }
    public boolean hasObservers() { return _bus.hasObservers(); }
}

Тогда у вас есть два способа использования RxBus. Создайте свой собственный класс приложения со ссылкой на RxBus или создайте RxBus в Activity/Fragment, а затем передайте его адаптеру. Я пользуюсь первым.

MyApp.java

public class MyApp extends Application {
    private static MyApp _instance;
    private RxBus _bus;
    public static MyApp get() {  return _instance; }

    @Override
    public void onCreate() {
        super.onCreate();
        _instance = this;
        _bus = new RxBus();
    }

    public RxBus bus() { return _bus; }
}

затем используйте

MyApp.get().bus() 

чтобы получить экземпляр RxBus.

Использование RxBus в Adpater было таким:

public class MyRecyclerAdapter extends ... {
    private RxBus _bus;

    public MykRecyclerAdapter (...) {
        ....
        _bus = MyApp.get().bus();
    }

    public ViewHolder onCreateViewHolder (...) {
        _sub = RxView.longClicks(itemView)  // You can use setOnLongClickListener as the same
              .subscribe(aVoid -> {
                        if (_bus.hasObservers()) { _bus.send(new SomeEvent(...)); }
                    });      
    }
}

Вы можете отправить любой класс с помощью _bus.send(), который мы получим в Activity:

RxBus bus = MyApp.get().bus();  // get the same RxBus instance
_sub = bus.toObserverable()
            .subscribe(e -> doSomething((SomeEvent) e));

По поводу отписаться.

В MyRecyclerAdapter вызовите _sub.unsubscribe() в методах очистки() и вызовите _sub.unsubscribe() в Activity onDestory().

@Override
public void onDestroy() {
    super.onDestroy();
    if (_adapter != null) {
        _adapter.cleanup();
    }
    if (_sub != null) {
         _sub.unsubscribe()
    }
}
person Putt    schedule 27.10.2016

Обычно нам нужен класс Pojo/Model из списка по выбранному индексу. Я делаю это следующим образом:

1) Создайте BaseRecyclerViewAdapter

abstract class BaseRecyclerViewAdapter<T> : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
    private val clickListenerPublishSubject = PublishSubject.create<T>()

    fun observeClickListener(): Observable<T> {
        return clickListenerPublishSubject
    }

    fun performClick(t: T?) {
        t ?: return
        clickListenerPublishSubject.onNext(t)
    }
}

2) В любом переходнике (Например MyAdapter)

class MyAdapter(private val events: List<Event>, context: Context) : BaseRecyclerViewAdapter<Event>() {
    //... Other methods of RecyclerView
    override fun onBindViewHolder(holder: RecyclerView.ViewHolder?, position: Int) {
        if (holder is EventViewHolder) {
            holder.eventBinding?.eventVm = EventViewModel(events[position])

            holder.eventBinding?.root?.setOnClickListener({ _ ->
                // This notifies the subscribers
                performClick(events[position])
            })
        }
    }
}

3) Внутри Activity или Fragment, где нужен прослушиватель кликов

myAdapter?.observeClickListener()
                ?.subscribe({ eventClicked ->
                    // do something with clicked item

                })
person Sandip Fichadiya    schedule 23.02.2018