Now handles sequences of immediate pushing and popping of controllers much better. Also guards against NPEs due to popping a controller during onAttach. Fixes #274
This commit is contained in:
@@ -8,6 +8,7 @@ import android.content.IntentSender;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Parcelable;
|
||||
import android.support.annotation.IdRes;
|
||||
import android.support.annotation.NonNull;
|
||||
@@ -86,6 +87,7 @@ public abstract class Controller {
|
||||
private final List<LifecycleListener> lifecycleListeners = new ArrayList<>();
|
||||
private final ArrayList<String> requestedPermissions = new ArrayList<>();
|
||||
private final ArrayList<RouterRequiringFunc> onRouterSetListeners = new ArrayList<>();
|
||||
private final Handler handler = new Handler();
|
||||
private WeakReference<View> destroyedView;
|
||||
private boolean isPerformingExitTransition;
|
||||
|
||||
@@ -817,7 +819,7 @@ public abstract class Controller {
|
||||
}
|
||||
}
|
||||
|
||||
private void attach(@NonNull View view) {
|
||||
private void attach(@NonNull final View view) {
|
||||
attachedToUnownedParent = router == null || view.getParent() != router.container;
|
||||
if (attachedToUnownedParent) {
|
||||
return;
|
||||
@@ -833,16 +835,23 @@ public abstract class Controller {
|
||||
attached = true;
|
||||
needsAttach = false;
|
||||
|
||||
onAttach(view);
|
||||
// This must be posted in a handler to ensure the system can finish attaching views if needed. Otherwise
|
||||
// we can get NPEs when developers decide to immediately pop this controller from onAttach.
|
||||
handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
onAttach(view);
|
||||
|
||||
if (hasOptionsMenu && !optionsMenuHidden) {
|
||||
router.invalidateOptionsMenu();
|
||||
}
|
||||
if (hasOptionsMenu && !optionsMenuHidden) {
|
||||
router.invalidateOptionsMenu();
|
||||
}
|
||||
|
||||
listeners = new ArrayList<>(lifecycleListeners);
|
||||
for (LifecycleListener lifecycleListener : listeners) {
|
||||
lifecycleListener.postAttach(this, view);
|
||||
}
|
||||
List<LifecycleListener> listeners = new ArrayList<>(lifecycleListeners);
|
||||
for (LifecycleListener lifecycleListener : listeners) {
|
||||
lifecycleListener.postAttach(Controller.this, view);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void detach(@NonNull View view, boolean forceViewRefRemoval, boolean blockViewRefRemoval) {
|
||||
|
||||
@@ -125,26 +125,37 @@ public abstract class Router {
|
||||
public boolean popController(@NonNull Controller controller) {
|
||||
ThreadUtils.ensureMainThread();
|
||||
|
||||
RouterTransaction topController = backstack.peek();
|
||||
boolean poppingTopController = topController != null && topController.controller == controller;
|
||||
RouterTransaction topTransaction = backstack.peek();
|
||||
boolean poppingTopController = topTransaction != null && topTransaction.controller == controller;
|
||||
|
||||
if (poppingTopController) {
|
||||
trackDestroyingController(backstack.pop());
|
||||
performControllerChange(backstack.peek(), topTransaction, false);
|
||||
} else {
|
||||
RouterTransaction removedTransaction = null;
|
||||
RouterTransaction nextTransaction = null;
|
||||
for (RouterTransaction transaction : backstack) {
|
||||
if (transaction.controller == controller) {
|
||||
if (controller.isAttached()) {
|
||||
trackDestroyingController(transaction);
|
||||
}
|
||||
backstack.remove(transaction);
|
||||
removedTransaction = transaction;
|
||||
} else if (removedTransaction != null) {
|
||||
if (!transaction.controller.isAttached()) {
|
||||
nextTransaction = transaction;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (poppingTopController) {
|
||||
performControllerChange(backstack.peek(), topController, false);
|
||||
if (removedTransaction != null) {
|
||||
performControllerChange(nextTransaction, removedTransaction, false);
|
||||
}
|
||||
}
|
||||
|
||||
if (popsLastView) {
|
||||
return topController != null;
|
||||
return topTransaction != null;
|
||||
} else {
|
||||
return !backstack.isEmpty();
|
||||
}
|
||||
|
||||
+10
@@ -167,6 +167,16 @@ public abstract class AnimatorChangeHandler extends ControllerChangeHandler {
|
||||
complete(changeListener, null);
|
||||
return;
|
||||
}
|
||||
if (needsImmediateCompletion) {
|
||||
if (from != null && (!isPush || removesFromViewOnPush)) {
|
||||
container.removeView(from);
|
||||
}
|
||||
complete(changeListener, null);
|
||||
if (isPush && from != null) {
|
||||
resetFromView(from);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
animator = getAnimator(container, from, to, isPush, toAddedToContainer);
|
||||
|
||||
|
||||
+16
-3
@@ -20,10 +20,11 @@ import com.bluelinelabs.conductor.ControllerChangeHandler;
|
||||
public abstract class TransitionChangeHandler extends ControllerChangeHandler {
|
||||
|
||||
public interface OnTransitionPreparedListener {
|
||||
public void onPrepared();
|
||||
void onPrepared();
|
||||
}
|
||||
|
||||
private boolean canceled;
|
||||
private boolean needsImmediateCompletion;
|
||||
|
||||
/**
|
||||
* Should be overridden to return the Transition to use while replacing Views.
|
||||
@@ -43,12 +44,24 @@ public abstract class TransitionChangeHandler extends ControllerChangeHandler {
|
||||
canceled = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void completeImmediately() {
|
||||
super.completeImmediately();
|
||||
|
||||
needsImmediateCompletion = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void performChange(@NonNull final ViewGroup container, @Nullable final View from, @Nullable final View to, final boolean isPush, @NonNull final ControllerChangeCompletedListener changeListener) {
|
||||
if (canceled) {
|
||||
changeListener.onChangeCompleted();
|
||||
return;
|
||||
}
|
||||
if (needsImmediateCompletion) {
|
||||
executePropertyChanges(container, from, to, null, isPush);
|
||||
changeListener.onChangeCompleted();
|
||||
return;
|
||||
}
|
||||
|
||||
final Transition transition = getTransition(container, from, to, isPush);
|
||||
transition.addListener(new TransitionListener() {
|
||||
@@ -109,10 +122,10 @@ public abstract class TransitionChangeHandler extends ControllerChangeHandler {
|
||||
* @param container The container these Views are hosted in
|
||||
* @param from The previous View in the container or {@code null} if there was no Controller before this transition
|
||||
* @param to The next View that should be put in the container or {@code null} if no Controller is being transitioned to
|
||||
* @param transition The transition with which {@code TransitionManager.beginDelayedTransition} has been called
|
||||
* @param transition The transition with which {@code TransitionManager.beginDelayedTransition} has been called. This will be null only if another ControllerChangeHandler immediately overrides this one.
|
||||
* @param isPush True if this is a push transaction, false if it's a pop
|
||||
*/
|
||||
public void executePropertyChanges(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, @NonNull Transition transition, boolean isPush) {
|
||||
public void executePropertyChanges(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, @Nullable Transition transition, boolean isPush) {
|
||||
if (from != null && (removesFromViewOnPush() || !isPush) && from.getParent() == container) {
|
||||
container.removeView(from);
|
||||
}
|
||||
|
||||
+16
@@ -7,6 +7,7 @@ import android.support.annotation.Nullable;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.bluelinelabs.conductor.Controller;
|
||||
import com.bluelinelabs.conductor.ControllerChangeHandler;
|
||||
import com.bluelinelabs.conductor.internal.ClassUtils;
|
||||
|
||||
@@ -78,4 +79,19 @@ public class TransitionChangeHandlerCompat extends ControllerChangeHandler {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAbortPush(@NonNull ControllerChangeHandler newHandler, @Nullable Controller newTop) {
|
||||
changeHandler.onAbortPush(newHandler, newTop);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void completeImmediately() {
|
||||
changeHandler.completeImmediately();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setForceRemoveViewOnPush(boolean force) {
|
||||
changeHandler.setForceRemoveViewOnPush(force);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+15
-6
@@ -15,7 +15,6 @@ import com.bluelinelabs.conductor.changehandler.TransitionChangeHandler;
|
||||
import com.bluelinelabs.conductor.demo.R;
|
||||
import com.bluelinelabs.conductor.demo.changehandler.transitions.FabTransform;
|
||||
import com.bluelinelabs.conductor.demo.util.AnimUtils;
|
||||
import com.bluelinelabs.conductor.demo.util.AnimUtils.TransitionEndListener;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public class FabToDialogTransitionChangeHandler extends TransitionChangeHandler {
|
||||
@@ -64,7 +63,7 @@ public class FabToDialogTransitionChangeHandler extends TransitionChangeHandler
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executePropertyChanges(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, @NonNull Transition transition, boolean isPush) {
|
||||
public void executePropertyChanges(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, @Nullable Transition transition, boolean isPush) {
|
||||
if (isPush) {
|
||||
fabParent.removeView(fab);
|
||||
container.addView(to);
|
||||
@@ -74,7 +73,7 @@ public class FabToDialogTransitionChangeHandler extends TransitionChangeHandler
|
||||
* Because otherwise we will be lost when trying to transition back.
|
||||
* Set it to invisible because we don't want it to jump back after the transition
|
||||
*/
|
||||
transition.addListener(new AnimUtils.TransitionEndListener() {
|
||||
AnimUtils.TransitionEndListener endListener = new AnimUtils.TransitionEndListener() {
|
||||
@Override
|
||||
public void onTransitionCompleted(Transition transition) {
|
||||
fab.setVisibility(View.GONE);
|
||||
@@ -82,19 +81,29 @@ public class FabToDialogTransitionChangeHandler extends TransitionChangeHandler
|
||||
fab = null;
|
||||
fabParent = null;
|
||||
}
|
||||
});
|
||||
};
|
||||
if (transition != null) {
|
||||
transition.addListener(endListener);
|
||||
} else {
|
||||
endListener.onTransitionCompleted(null);
|
||||
}
|
||||
} else {
|
||||
dialogBackground.setVisibility(View.INVISIBLE);
|
||||
fabParent.addView(fab);
|
||||
container.removeView(from);
|
||||
|
||||
transition.addListener(new TransitionEndListener() {
|
||||
AnimUtils.TransitionEndListener endListener = new AnimUtils.TransitionEndListener() {
|
||||
@Override
|
||||
public void onTransitionCompleted(Transition transition) {
|
||||
fabParent.removeView(dialogBackground);
|
||||
dialogBackground = null;
|
||||
}
|
||||
});
|
||||
};
|
||||
if (transition != null) {
|
||||
transition.addListener(endListener);
|
||||
} else {
|
||||
endListener.onTransitionCompleted(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -95,7 +95,7 @@ public class SharedElementDelayingChangeHandler extends ArcFadeMoveChangeHandler
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executePropertyChanges(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, @NonNull Transition transition, boolean isPush) {
|
||||
public void executePropertyChanges(@NonNull ViewGroup container, @Nullable View from, @Nullable View to, @Nullable Transition transition, boolean isPush) {
|
||||
if (to != null) {
|
||||
to.setVisibility(View.VISIBLE);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user