material-foundation / material-remixer-android

Remixer for Android. Live adjustment of app variables.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Write tests for crashes on RemixerFragment.showRemixer() being called too quickly.

pingpongboss opened this issue · comments

This is quite easy to do on a slow device (or emulator).

First attach RemixerFragment to a button

RemixerFragment remixerFragment = RemixerFragment.newInstance();
remixerFragment.attachToButton(this, (Button) findViewById(R.id.remixer_button));

Then tap on the button twice quickly. On a sufficiently slow device or emulator, you will get this crash:

11-02 18:16:56.269  6040  6040 E AndroidRuntime: FATAL EXCEPTION: main
11-02 18:16:56.269  6040  6040 E AndroidRuntime: Process: com.google.android.material.motion.family.rebound.sample, PID: 6040
11-02 18:16:56.269  6040  6040 E AndroidRuntime: java.lang.IllegalStateException: Fragment already added: RemixerFragment{2090b38 #0 Remixer}
11-02 18:16:56.269  6040  6040 E AndroidRuntime: 	at android.support.v4.app.FragmentManagerImpl.addFragment(FragmentManager.java:1361)
11-02 18:16:56.269  6040  6040 E AndroidRuntime: 	at android.support.v4.app.BackStackRecord.run(BackStackRecord.java:734)
11-02 18:16:56.269  6040  6040 E AndroidRuntime: 	at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1677)
11-02 18:16:56.269  6040  6040 E AndroidRuntime: 	at android.support.v4.app.FragmentManagerImpl$1.run(FragmentManager.java:536)
11-02 18:16:56.269  6040  6040 E AndroidRuntime: 	at android.os.Handler.handleCallback(Handler.java:751)
11-02 18:16:56.269  6040  6040 E AndroidRuntime: 	at android.os.Handler.dispatchMessage(Handler.java:95)
11-02 18:16:56.269  6040  6040 E AndroidRuntime: 	at android.os.Looper.loop(Looper.java:154)
11-02 18:16:56.269  6040  6040 E AndroidRuntime: 	at android.app.ActivityThread.main(ActivityThread.java:6077)
11-02 18:16:56.269  6040  6040 E AndroidRuntime: 	at java.lang.reflect.Method.invoke(Native Method)
11-02 18:16:56.269  6040  6040 E AndroidRuntime: 	at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:865)
11-02 18:16:56.269  6040  6040 E AndroidRuntime: 	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:755)

Remixer should be hardened against this simple error.

commented

I've looked into this.
It seems as though isAdded does not give accurate values for sufficiently fast calls of show (there's a delay).

I discovered this while building the requested shake feature ( #112 ) - where if a shake is a high enough frequency, show is called too quickly and an IllegalStateException is thrown.

I can mitigate this on my end using a high-pass filter on the accelerometer data - but preferably we'd either debounce show or get an accurate read on whether the fragment is currently added.

I've tried overriding the show method and using the transactional interface (which is used under the hood of DialogFragment's show method anyway, but I tried to reconfigure it to replace the fragment anyway), but the error is still thrown.

Any ideas?

Github said email replies don't support markdown... soooo

Thanks for taking the time to look into this Nick!

The sad part is that there is a method on FragmentTransaction that does
exactly what we need, commitNow, but it's on API 24+, we want to support
15+, so that's out of the question.

The other thing I can think of, which is a tad (very) hacky is to use a
isAddingFragment boolean (with all accesses guarded by a synchronized
block) to keep track of one such instance, it would be set to true as soon
as the show method is about to be called, and then set to false on one of
the lifecycle methods (either onStart or onResume, not sure).

private Object syncLock = new Object();
private boolean isAddingFragment
public void showRemixer() {
  synchronized(syncLock) {
    if (!isAddingFragment) {
      isAddingFragment = true;
      show(.....);
    }
  }
}

public void onStart/onResume(...) {
  synchronized(syncLock) {
    isAddingFragment = false;
  }
}

Maybe even do it on onStop and call it "isFragmentAddedToStack"? dunno.

Let me know if you try something like this.

Thanks!!

commented

Good timing - right before I submitted my pull request / gave up on show for the moment I started writing something very similar with synchronized blocks. I figured some sort of lock was ideal, but didn't complete it. I'll keep heading in this direction and update as I go.

commented

onResume happens later in the fragment lifecycle I believe, so I am rolling with that. Pulling changes now.

commented

Seems fixed! This should be closed, no?

I Think I tested it pretty thoroughly manually but would like to write actual tests (the reason being - I would also test sensor functionality).

I assume a new issue should be opened for that.

Let's repurpose this bug for that purpose, @nicksahler, that way we keep the context.