Search code examples
androidrx-androidonbackpressed

Clicking back button twice to exit an activity with rxjava


Looking for a subtle rx approach to exit an activity while pressing back button two times.

boolean doubleBackToExitPressedOnce = false;

@Override
public void onBackPressed() {
    if (doubleBackToExitPressedOnce) {
        super.onBackPressed();
        return;
    }

    this.doubleBackToExitPressedOnce = true;
    Toast.makeText(this, "Please click BACK again to exit", Toast.LENGTH_SHORT).show();

    new Handler().postDelayed(new Runnable() {

        @Override
        public void run() {
            doubleBackToExitPressedOnce=false;                       
        }
    }, 2000);
} 

Solution

  • I would suggest slightly different approach. Actually, what we are looking for, is time between 2 clicks. RxJava has operator interval(TimeUnit), which gives us exactly what we want. So, we have our subject and desired exit timeout.

    private static final long EXIT_TIMEOUT = 2000;
    private CompositeDisposable compositeDisposable = new CompositeDisposable();
    private PublishSubject<Boolean> backButtonClickSource = PublishSubject.create();
    

    In onResume we add operators to our subject and subscribe to it. Result we are adding to CompositeDisposable, to be able to dispose it later with all other subscriptions you probably have in your activity.

    @Override
    protected void onResume() {
        super.onResume();
    
        compositeDisposable.add(backButtonClickSource
                .debounce(100, TimeUnit.MILLISECONDS)
                .observeOn(AndroidSchedulers.mainThread())
                .doOnNext(new Consumer<Boolean>() {
                    @Override
                    public void accept(@NonNull Boolean event) throws Exception {
                        Toast.makeText(MainActivity.this, "Please press back once more to exit", Toast.LENGTH_SHORT).show();
                    }
                })
                .timeInterval(TimeUnit.MILLISECONDS)
                .skip(1)
                .filter(new Predicate<Timed<Boolean>>() {
                    @Override
                    public boolean test(@NonNull Timed<Boolean> interval) throws Exception {
                        return interval.time() < EXIT_TIMEOUT;
                    }
                })
                .subscribe(new Consumer<Timed<Boolean>>() {
                    @Override
                    public void accept(@NonNull Timed<Boolean> interval) throws Exception {
                        finish();
                    }
                }));
    }
    

    We use debounce to get rid of noise (maybe unnecessary). Then on each click we show message to user (whatever you want, for sake of simplicity I used Toast). Before we switch to main thread, otherwise exception will be thrown. First event we skip, because otherwise time interval between subscribe and first click will be emitted, and if it's small enough, exit will happen after just one click. All the intervals that are bigger than our EXIT_TIMEOUT we filter out. And finally, when we get small enough time interval, which is not first, we finish our activity.

    Then, in onPause we should clear our CompositeDisposable in order not to get click events anymore.

    @Override
    protected void onPause() {
        super.onPause();
    
        compositeDisposable.clear();
    }
    

    And of course in onBackPressed() we should forward back button clicks to our PublishSubject.

    @Override
    public void onBackPressed() {
        backButtonClickSource.onNext(true);
    }