ViewRootImpl.setPausedForTransition(boolean) NullPointerException in ActivityTransitionCoordinator when transition to other Activity invoked too early
Asked Answered
L

2

10

In my Android app, I have a splash screen where I do some setup and loading. My app uses default explode as a windowEnterTransition and a windowExitTransition and a changeImageTransform plus changeBounds transition set as a windowSharedElementEnterTransition and windowSharedElementExitTransition. For convenience, I start the next Activity using a static method where I pass the current Activity as a Context and a shared element. The code is provided in the second part of this post.

One of the scenarios is that there is nothing to load, so app almost immediately fires the next Activity. The problem is that in this case app somehow enigmatically crashes in an ActivityTransitionCoordinator with a stack given in the next part of this post. Debugging of the internals shows that ViewRootImpl that is achieved there is null and there is no null check, so invoking viewRoot.setPausedForTransition(false) throws a NullPointerException. You can find this unlucky spot marked in the code below.

To focus on this failing scenario, let’s make an assumption that a logic deciding that there is nothing to load and the next Activity should be started immediately is so simple, that it could be simplified to just starting the mentioned activity.

It makes no difference if a start of the second Activity is invoked in onCreate(), onResume(), or onEnterAnimationComplete() method. I even tried adding a listener on a Transition acquired by calling getWindow().getSharedElementEnterTransition() and getWindow().getEnterTransition() to be allowed to start the next Activity when transitions are finished. The given Transitions are not null, but app never goes into methods of attached listeners.

The workaround I use now is just to schedule a Runnable to make an invoking of the next Activity delayed.

I’m wondering if this is an Android (a SupportLibrary to be more specific) issue, or I missed something. Has anyone encountered a similar problem?

A stacktrace:

08-12 00:35:32.550 26453-26453/com.faver.mkoslacz.faverdemo E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.faver.mkoslacz.faverdemo, PID: 26453
java.lang.NullPointerException: Attempt to invoke virtual method 'void android.view.ViewRootImpl.setPausedForTransition(boolean)' on a null object reference
at android.app.ActivityTransitionCoordinator.startInputWhenTransitionsComplete(ActivityTransitionCoordinator.java:897)
at android.app.ActivityTransitionCoordinator.viewsTransitionComplete(ActivityTransitionCoordinator.java:885)
at android.app.ExitTransitionCoordinator.getExitTransition(ExitTransitionCoordinator.java:318)
at android.app.ExitTransitionCoordinator.beginTransitions(ExitTransitionCoordinator.java:365)
at android.app.ExitTransitionCoordinator.-wrap0(ExitTransitionCoordinator.java)
at android.app.ExitTransitionCoordinator$4.run(ExitTransitionCoordinator.java:216)
at android.app.ActivityTransitionCoordinator.startTransition(ActivityTransitionCoordinator.java:773)
at android.app.ExitTransitionCoordinator.startExit(ExitTransitionCoordinator.java:213)
at android.app.ActivityTransitionState.startExitOutTransition(ActivityTransitionState.java:317)
at android.app.Activity.cancelInputsAndStartExitTransition(Activity.java:3960)
at android.app.Activity.startActivityForResult(Activity.java:3936)
at android.support.v4.app.BaseFragmentActivityJB.startActivityForResult(BaseFragmentActivityJB.java:48)
at android.support.v4.app.FragmentActivity.startActivityForResult(FragmentActivity.java:75)
at android.app.Activity.startActivity(Activity.java:4196)
at com.faver.mkoslacz.faverdemo.activity.AuthorizationActivity.startWithTransiton(AuthorizationActivity.java:45)
at com.faver.mkoslacz.faverdemo.activity.SplashActivity.onEnterAnimationComplete(SplashActivity.java:27)
at android.app.Activity.dispatchEnterAnimationComplete(Activity.java:5852)
at android.app.ActivityThread.handleEnterAnimationComplete(ActivityThread.java:2668)
at android.app.ActivityThread.-wrap10(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1558)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5417)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)

A code failing in ActivityTransitionAnimator:

private void startInputWhenTransitionsComplete() {
    if (mViewsTransitionComplete && mSharedElementTransitionComplete) {
        final View decor = getDecor();
        if (decor != null) {
            final ViewRootImpl viewRoot = decor.getViewRootImpl(); // it's null
            viewRoot.setPausedForTransition(false); // crashes here
        }
        onTransitionsComplete();
    }
}

A method starting the next Activity:

public static void startWithTransiton(Activity activity, android.view.View logo) {
    Intent intent = new Intent(activity, AuthorizationActivity.class);
    ActivityOptionsCompat options = ActivityOptionsCompat
            .makeSceneTransitionAnimation(
                    activity,
                    logo,
                    activity.getString(R.string.logoTransfer));
    activity.startActivity(intent, options.toBundle());
}

The splash Activity contents (simplified):

public class SplashActivity extends AppCompatActivity {

    private static final String TAG = "SplashActivity";

    private View logo;
    

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_splash);
        logo = findViewById(R.id.logo);

//        AuthorizationActivity.startWithTransiton(this, logo); // will fail

        new Handler().postDelayed(() -> {
            AuthorizationActivity.startWithTransiton(this, logo); // executes flawlessly
        }, 300);

        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
            Transition sharedElementEnterTransition = getWindow().getSharedElementEnterTransition();
            if (sharedElementEnterTransition != null) {
                sharedElementEnterTransition.addListener(new Transition.TransitionListener() {
                    @Override
                    public void onTransitionStart(Transition transition) {
                        Log.d(TAG, "onTransitionStart: never executes");
                    }

                    @Override
                    public void onTransitionEnd(Transition transition) {
                        Log.d(TAG, "onTransitionEnd: never executes");
                    }

                    @Override
                    public void onTransitionCancel(Transition transition) {
                        Log.d(TAG, "onTransitionCancel: never executes");
                    }

                    @Override
                    public void onTransitionPause(Transition transition) {
                        Log.d(TAG, "onTransitionPause: never executes");
                    }

                    @Override
                    public void onTransitionResume(Transition transition) {
                        Log.d(TAG, "onTransitionResume: never executes");
                    }
                });
            }

            Transition enterTransition = getWindow().getEnterTransition();
            if (enterTransition != null) {
                enterTransition.addListener(new Transition.TransitionListener() {
                    @Override
                    public void onTransitionStart(Transition transition) {
                        Log.d(TAG, "onTransitionStart: never executes");
                    }

                    @Override
                    public void onTransitionEnd(Transition transition) {
                        Log.d(TAG, "onTransitionEnd: never executes");
                    }

                    @Override
                    public void onTransitionCancel(Transition transition) {
                        Log.d(TAG, "onTransitionCancel: never executes");
                    }

                    @Override
                    public void onTransitionPause(Transition transition) {
                        Log.d(TAG, "onTransitionPause: never executes");
                    }

                    @Override
                    public void onTransitionResume(Transition transition) {
                        Log.d(TAG, "onTransitionResume: never executes");
                    }
                });
            }
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
//        AuthorizationActivity.startWithTransiton(this, logo); // will fail
    }

    @Override
    public void onEnterAnimationComplete() {
        super.onEnterAnimationComplete();
//        AuthorizationActivity.startWithTransiton(this, logo); // will fail
    }
}
Lannie answered 13/8, 2016 at 11:50 Comment(3)
See my comment on a similar question http://stackoverflow.com/questions/36557479/...Regent
@Regent Actually, applying your suggestion makes my animations do not work. My workaround postpones them, but they work at least. Sorry :(Lannie
it works for me though so idkRegent
M
5

I'm also facing same issue while working with Explode Activity transition,I find one thing this code running normally in Lollipop version, But crashed above Lollipop. Even i can't find out the reason for crashing. But i resolve it another way. Just put delay in activity transition. I given my code below

public class SplashActivity extends AppCompatActivity {

final Handler handler = new Handler();

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_splash);
    handler.postDelayed(new Runnable() {
        @Override
        public void run() {
            navigate();
        }
    },500);
}

private void navigate() {
    ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(this);
    Intent intent = new Intent(SplashActivity.this, MainActivity.class);
    startActivity(intent, options.toBundle());
}}

After adding this working normally in all version.

Mutism answered 29/8, 2017 at 11:34 Comment(1)
Well, that's exactly the workaround I described in my question.Lannie
Q
0

Instead of using a Handler you can navigate to the next activity using your root view post method

findViewById(<Your activity root view id>).post(() -> navigate());

In general, the root view could be android.R.id.content

Quadrivial answered 15/4, 2020 at 16:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.