Skip to content
This repository has been archived by the owner on May 15, 2023. It is now read-only.

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

Open
pingpongboss opened this issue Nov 3, 2016 · 6 comments

Comments

@pingpongboss
Copy link
Contributor

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.

@microfauna
Copy link
Contributor

microfauna commented Jan 12, 2017

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?

@miguelandres
Copy link
Contributor

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!!

@microfauna
Copy link
Contributor

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.

@microfauna
Copy link
Contributor

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

@microfauna
Copy link
Contributor

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.

@miguelandres miguelandres changed the title RemixerFragment crashes if show() is called twice Write tests for crashes on RemixerFragment.showRemixer() being called too quickly. Jan 19, 2017
@miguelandres
Copy link
Contributor

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

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

3 participants