How to handle Item clicks for a recycler view using RxJava

后端 未结 5 685
有刺的猬
有刺的猬 2021-02-01 21:49

I was interested to find out what is the best way to respond to a item click of a recycler view.

Normally I would add a onclick() listener to the ViewHolder and pass ba

相关标签:
5条回答
  • 2021-02-01 22:33

    I would suggest you to do with your initial aproach of an observable per element on click, but in order to avoid create a new observable every time you can just cache the items emitted the first time using cache.

          /**
     * 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));
    
    }
    
    0 讨论(0)
  • 2021-02-01 22:40

    My solution was much like @epool 's except use EventBus model.

    First, create a 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(); }
    }
    

    Then, you have two way to use RxBus. Create your custom Application class with RxBus reference or create RxBus in Activity/Fragment then pass it to adapter. I'm use the first.

    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; }
    }
    

    then use

    MyApp.get().bus() 
    

    to get RxBus instance.

    The usage of RxBus in Adpater was like this:

    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(...)); }
                        });      
        }
    }
    

    You can send any class with _bus.send(), which We will recieve in the Activity:

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

    About unsubscribe.

    In MyRecyclerAdapter call _sub.unsubscribe() in clearup() methods and call _sub.unsubscribe() in Activity's onDestory().

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (_adapter != null) {
            _adapter.cleanup();
        }
        if (_sub != null) {
             _sub.unsubscribe()
        }
    }
    
    0 讨论(0)
  • 2021-02-01 22:43

    Step 1: Move the business logic out of the activities to domain classes/services

    Optional: Use https://github.com/roboguice/roboguice to easily wire up your services with each other.

    Step 2: @Inject (or just set) your service into your adapter

    Step 3: Grab https://github.com/JakeWharton/RxBinding and use the super powers in your adapter:

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

    Step 4: Get a runtime crash and learn how to deal with subscriptions

    Step 5: PROFIT :)

    0 讨论(0)
  • 2021-02-01 22:50

    We generally need the Pojo/Model class from list on clicked index. I do it in following way:

    1) Create a 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) In any adapter (For example 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) Inside the Activity or Fragment where click listener is needed

    myAdapter?.observeClickListener()
                    ?.subscribe({ eventClicked ->
                        // do something with clicked item
    
                    })
    
    0 讨论(0)
  • 2021-02-01 22:51

    You can use RxBinding and then create a subject inside of your adapter, then redirect all the events to that subject and just create a getter of the subject to act as an observable and finally just subscribe you on that observable.

    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;
    }
    

    An usage example could be:

    mMyAdapter.getViewClickedObservable()
            .subscribe(view -> /* do the action. */);
    
    0 讨论(0)
提交回复
热议问题