-->

Clicking back button twice to exit an activity wit

2019-07-03 18:45发布

问题:

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

回答1:

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


回答2:

private Disposable backPressedDisposable;
private BehaviorSubject<Long> backPressedSubject = BehaviorSubject.createDefault(0L); // init with 0

@Override
protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);

 backPressedDisposable = backPressedSubject
   .buffer(2, 1)
   .map(it -> new Pair<>(it.get(0), it.get(1)))
   .map(pair -> pair.second - pair.first < TimeUnit.SECONDS.toMillis(2)) // 2 second
   .observeOn(AndroidSchedulers.mainThread())
   .subscribe(willFinish -> {
     if (willFinish) {
       finish();
     }
     else {
       Toast.makeText(getApplicationContext(), "press once more will exit", Toast.LENGTH_SHORT).show();
     }
   });
}

@Override
public void onBackPressed() {
 backPressedSubject.onNext(System.currentTimeMillis()); // add current time
}

@Override
protected void onDestroy() {
 super.onDestroy();

 try {
   backPressedDisposable.dispose();
 } catch (Exception ignore) {}
}


回答3:

PublishSubject<Boolean> clickSource = PublishSubject.create();
clickSource.debounce(100, TimeUnit.MILLISECONDS)
.take(2)
.subscribe(t -> MyActivity.this.finish()};

Then feed click events to clickSource from listener or use RxBindings library:

btnExit.setOnClickListener(v -> clickSource.onNext(true));

There is also .window() operator you can use for opening 'event valve' for a limited time after first click and closing it if second click not received