Internal exception/crash when quickly tapping Android's overview button twice

Viewed 129

I have a fragment called CustomArFragment (subclass of ArFragment from Sceneform 1.15) contained in MainActivity. At some point in time, I reset the Fragment by replacing with a new instance. I do this by calling the following method in MainActivity:

fun reset() {
        val oldFragment = (supportFragmentManager.findFragmentById(R.id.custom_ar_fragment)) as? CustomArFragment ?: return
        val newArFragment = CustomArFragment()

        val transaction = supportFragmentManager.beginTransaction()
        transaction.remove(oldFragment)
        transaction.add(R.id.custom_ar_fragment, newArFragment)
        transaction.commit()
}

Now, as I said the replacement at first seems to work properly and the new fragment is running as usual upon the replacement. However, if I at any point in time after calling reset() press the android overview button twice in rapid succession I receive the following error which seems to be related to some internal calls that I haven't been able to trace:

java.lang.NullPointerException: throw with null exception
        at com.google.ar.sceneform.utilities.Preconditions.checkNotNull(SourceFile:3)
        at com.google.ar.sceneform.SceneView.onLayout(SourceFile:41)
        at com.google.ar.sceneform.ArSceneView.onLayout(SourceFile:79)
        at android.view.View.layout(View.java:20729)
        at android.widget.FrameLayout.layoutChildren(FrameLayout.java:323)
        at android.widget.FrameLayout.onLayout(FrameLayout.java:261)
        at android.view.View.layout(View.java:20729)
        at android.view.ViewGroup.layout(ViewGroup.java:6198)
        at android.widget.FrameLayout.layoutChildren(FrameLayout.java:323)
        at android.widget.FrameLayout.onLayout(FrameLayout.java:261)
        at android.view.View.layout(View.java:20729)
        at android.view.ViewGroup.layout(ViewGroup.java:6198)
        at androidx.constraintlayout.widget.ConstraintLayout.onLayout(ConstraintLayout.java:1915)
        at android.view.View.layout(View.java:20729)
        at android.view.ViewGroup.layout(ViewGroup.java:6198)
        at android.widget.FrameLayout.layoutChildren(FrameLayout.java:323)
        at android.widget.FrameLayout.onLayout(FrameLayout.java:261)
        at android.view.View.layout(View.java:20729)
        at android.view.ViewGroup.layout(ViewGroup.java:6198)
        at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1812)
        at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1656)
        at android.widget.LinearLayout.onLayout(LinearLayout.java:1565)
        at android.view.View.layout(View.java:20729)
        at android.view.ViewGroup.layout(ViewGroup.java:6198)
        at android.widget.FrameLayout.layoutChildren(FrameLayout.java:323)
        at android.widget.FrameLayout.onLayout(FrameLayout.java:261)
        at android.view.View.layout(View.java:20729)
        at android.view.ViewGroup.layout(ViewGroup.java:6198)
        at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1812)
        at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1656)
        at android.widget.LinearLayout.onLayout(LinearLayout.java:1565)
        at android.view.View.layout(View.java:20729)
        at android.view.ViewGroup.layout(ViewGroup.java:6198)
        at android.widget.FrameLayout.layoutChildren(FrameLayout.java:323)
        at android.widget.FrameLayout.onLayout(FrameLayout.java:261)
        at com.android.internal.policy.DecorView.onLayout(DecorView.java:757)
        at android.view.View.layout(View.java:20729)
        at android.view.ViewGroup.layout(ViewGroup.java:6198)
        at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:2859)
        at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2386)
        at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1524)
        at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:7398)
        at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1092)
        at android.view.Choreographer.doCallbacks(Choreographer.java:888)
        at android.view.Choreographer.doFrame(Choreographer.java:819)
        at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1078)
        at android.os.Handler.handleCallback(Handler.java:873)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:201)
        at android.app.ActivityThread.main(ActivityThread.java:6815)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:547)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:873)

Now, I am unsure if this is an error related to Sceneform or if it is something wrong with the way I am replacing my fragment.

I want to clarify that the replacement is working perfectly unless I double tap the app overview button after the replacement has taken place, and that I'm not doing anything in the CustomArFragment's life cycle methods that might interfere with the replacement. So If I press the overview button and then wait for a second or two before pressing it again to open the app back up everything is working as intended without crashing.

Help is greatly appreciated, I have ran out of ideas of what might be causing this!

1 Answers

Crash seems to be related to the fact that some ARCore/Sceneform resources is kept running for some time upon removing the old fragment, but still referencing the removed fragment (It was a while ago I solved this issue so I don't remember the exact reason but I think it was this).

Anyway, in order to avoid the crash the only thing I found to be working is adding the removed fragment to the backstack. That way sceneform/ARCore will not encounter a null pointer.

    val oldFragment = (supportFragmentManager.findFragmentById(R.id.custom_ar_fragment)) as? CustomArFragment ?: return
    oldFragment.arSceneView.pause()
    val newArFragment = CustomArFragment()

    val transaction = supportFragmentManager.beginTransaction().remove(oldFragment).add(R.id.sceneform_fragment, newArFragment).addToBackStack(null)
    transaction.commit()
    supportFragmentManager.executePendingTransactions()

Of course, it is not ideal to have the old fragments saved to the backstack since I never intend to return to them. However, since I assume the number of resets of the fragment will be low it's working good enough for me. But if you find a better solution for resetting an ArFragment please let me know.

Related