Zhuinden/flowless

Possible randomly occurring freeze where `pendingTraversal != null, state == DISPATCHED`

Zhuinden opened this issue · 5 comments

There can be a traversal in which nextHistory == null and state == DISPATCHED.

Image

Flowless1.0-alpha1.1 does not handle this special case

    void setDispatcher(@NonNull Dispatcher dispatcher, boolean created) {
        this.dispatcher = checkNotNull(dispatcher, "dispatcher");

        if(pendingTraversal == null && created) {
            // initialization should occur for current state if views are created or re-created
            move(new PendingTraversal() {
                @Override
                void doExecute() {
                    bootstrap(history);
                }
            });
        } else if(pendingTraversal == null) { // no-op if the view still exists and nothing to be done
            return;
        } else if(pendingTraversal.state == TraversalState.DISPATCHED) { // a traversal is in progress
            // do nothing, pending traversal will finish

created == true, but pendingTraversal != null, but the pending traversal will never finish.

The newly added fix for the midflight reentrance test might help

  if((pendingTraversal == null && created) || (pendingTraversal != null && pendingTraversal.state == TraversalState.DISPATCHED && pendingTraversal.next == null)) {

However, the traversal would only call traversal.next if onTraversalCompleted() is called, which in this case might be never.

This special case must be analyzed further and fixed.

  • Must find the root cause of why this traversal can occur
  • Kill root cause

Hopefully fixed with 1.0-alpha1.2

Not fixed. Attempted fixes failed, so 1.0-alpha1.5 might still have this.

Will need to reproduce through test, and eliminate the error, rather than just sharpshooting.

This probably came over from Flow 1.0-alpha itself.

The error is not in Flow 1.0-alpha, it's in SingleRootDispatcher.

If two rotations are done during a state transition, onMeasure() is never called.

    ViewUtils.waitForMeasure(newView, new ViewUtils.OnMeasuredCallback() {
        @Override
        public void onMeasured(View view, int width, int height) {
            Animator animator = DispatcherUtils.createAnimatorForViews(animatedKey, previousView, newView, direction);
            if(animator != null) {
                animator.addListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        finishTransition(previousView, root, callback);
                    }
                });
                animator.start();
            } else {
                finishTransition(previousView, root, callback);
            }
        }
    });

Sometimes, onMeasured() is never called, and as a result, the state transition will never finish.

Well... technically there is more to it than that.

Apparently the issue is that the dispatcher can be removed midway, and the onMeasure() won't be called.

There is another issue that the InternalLifecycleIntegration fragment isn't properly bound by application.registerLifecycleCallbacks() if the screen is rotated while the app is still starting up.. but I haven't been able to reproduce it reliably.