Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

OnClickListener fired after onPause?

The project that I'm working uses a view-presenter abstraction. Here is a simplified version of all the main classes.

The abstract activity (wire Presenter instance, with View)

public abstract class MvpActivity<Presenter extends MvpPresenter>
        extends ActionBarActivity {

  protected Presenter mPresenter;

  @Override protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mPresenter = getPresenterInstance();
  }

  @Override protected void onResume() {
    super.onResume();
    mPresenter.onResume(this);
  }

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

The view interface

public interface MyView {
  void redirect();
}

The view implementation

public class MyActivity
        extends MvpActivity<MyPresenter>
        implements MyView {

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

    Button myButton = (Button)findViewById(R.id.my_button);

    myButton.setOnClickListener(v -> mPresenter.onButtonPressed());
  }

  @Override protected MyPresenter getPresenterInstance() {
    return new MyPresenter();
  }

  @Override void redirect(){
    startActivity(new Intent(this, MyOtherActivity.class));
  }

The abstract presenter

public abstract class MvpPresenter<ViewType> {

  private ViewType mView;

  public void onResume(ViewType view) {
    mView = view;
  }

  public void onPause() {
    mView = null;
  }

  protected ViewType getView() {
    if (mView == null) {
      throw new IllegalStateException("Presenter view is null");
    }
    return mView;
  }
}

And the presenter implementation

public class MyPresenter extends MvpPresenter<MyView> {

  @Override public void onResume(MyView myView){
    super.onResume(myView);
    Log.("MyPresenter", "Presenter resumed");
  }

  @Override public void onPause(){
    super.onPause()
    Log.("MyPresenter", "Presenter paused");
  }

  public void onButtonPressed(){
    getView().redirect();
  }
}

The issue comes up as an "IllegalStateException: Presenter view is null" triggered by getView().redirect(); when called from the MyPresenter.onButtonPressed() method.

This doesn't make any sense to me, as the view should always be not null if the listener is fired. The view is only set to null if the MvpPresenter.onPause() is executed which is only being called from MvpActivity.onPause(). I wouldn't expect to receive any click events after this happens, so what am I missing here?

Sadly, I can not reproduce this issue by manually testing the application. The reports are coming in from Crashlytics.

Note: retrolambda is in use for the button click listener

Update 10/07/2017

Some ways of fixing this issues:

- https://developer.android.com/reference/android/view/View.html#cancelPendingInputEvents()

- https://github.com/JakeWharton/butterknife/blob/master/butterknife/src/main/java/butterknife/internal/DebouncingOnClickListener.java

like image 814
Robert Estivill Avatar asked Jul 15 '15 13:07

Robert Estivill


1 Answers

Short answer: don't do that.

Unfortunately, you're relying on an order of events that is undefined. Activity lifecycle events and Window events are two different things, even though they're often closely related. You'll get onPause() when the activity is paused for any reason. But the View touch events aren't unhooked until the View's window loses focus.

It's very common for an activity to pause right when its window loses focus--for instance, when the screen is locked or when another activity is launched. But as you've seen, you can get pauses without a focus change and focus changes without a pause. Even when the two events occur together, there's a narrow window of time when onPause() has been called but the window input handlers are still active.

As with any undefined behavior, the actual results you see will vary by OS version and hardware type.

If you need to make sure that you don't receive View messages after onPause, you should unhook your handlers in onPause.

like image 172
Ian Ni-Lewis Avatar answered Nov 03 '22 01:11

Ian Ni-Lewis