Skip to content

Commit

Permalink
Emit componentDidAppear after appear animation completes (#6331)
Browse files Browse the repository at this point in the history
This PR addresses a long lasting tech debt on Android where `componentDidAppear` event was emitted prematurely.

RNN's visibility events are modeled after iOS's visibility events: `viewWillAppear`, `viewDidAppear`, `viewWillDisappear` and `viewDidDisappear`. Initially all four visibility events were implemented in RNN.
`willAppear` and `willDisappear` events were removed in v2 since we realised that the async nature of RN and RNN rendered these two events unreliable as we couldn't guarantee they will be handled in JS in the appropriate time.

V2 included another change, only on Android, where `componentDidAppear` was emitted  when screens were attached to the hierarchy instead of when their appear animation (push, showModal) ended. This completely defeats the purpose of the event which is to let the user delay work until the screen has settled, thus reducing load on the js/main thread during the critical period of screen creation and display.

* Reintroduce parity between iOS and Android - `componentDidAppear` is emitted after the appear animation ends.
* `componentDidAppear` for TopBar components (buttons, title, background component) is still emitted when they are attached to the screen.

closes #6261
  • Loading branch information
guyca authored Jul 1, 2020
1 parent c7509c5 commit b9310bf
Show file tree
Hide file tree
Showing 44 changed files with 394 additions and 254 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@
import static com.reactnativenavigation.utils.CollectionUtils.*;

@SuppressWarnings("ResourceType")
public class NavigationAnimator extends BaseAnimator {
public class StackAnimator extends BaseAnimator {

private final ElementTransitionManager transitionManager;
private Map<View, Animator> runningPushAnimations = new HashMap<>();

public NavigationAnimator(Context context, ElementTransitionManager transitionManager) {
public StackAnimator(Context context, ElementTransitionManager transitionManager) {
super(context);
this.transitionManager = transitionManager;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ public class OverlayManager {
public void show(ViewGroup overlaysContainer, ViewController overlay, CommandListener listener) {
overlaysContainer.setVisibility(View.VISIBLE);
overlayRegistry.put(overlay.getId(), overlay);
overlay.addOnAppearedListener(() -> listener.onSuccess(overlay.getId()));
overlay.addOnAppearedListener(() -> {
overlay.onViewDidAppear();
listener.onSuccess(overlay.getId());
});
overlaysContainer.addView(overlay.getView(), matchParentWithBehaviour(new BehaviourDelegate(overlay)));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import android.content.Context;

import com.facebook.react.ReactInstanceManager;
import com.reactnativenavigation.anim.NavigationAnimator;
import com.reactnativenavigation.anim.StackAnimator;
import com.reactnativenavigation.parse.Options;
import com.reactnativenavigation.utils.CommandListener;
import com.reactnativenavigation.viewcontrollers.ViewController;
Expand All @@ -16,7 +16,7 @@
import static com.reactnativenavigation.utils.CoordinatorLayoutUtils.matchParentWithBehaviour;

public class RootPresenter {
private NavigationAnimator animator;
private StackAnimator animator;
private CoordinatorLayout rootLayout;
private LayoutDirectionApplier layoutDirectionApplier;

Expand All @@ -25,11 +25,11 @@ public void setRootContainer(CoordinatorLayout rootLayout) {
}

public RootPresenter(Context context) {
this(new NavigationAnimator(context, new ElementTransitionManager()), new LayoutDirectionApplier());
this(new StackAnimator(context, new ElementTransitionManager()), new LayoutDirectionApplier());
}

@VisibleForTesting
public RootPresenter(NavigationAnimator animator, LayoutDirectionApplier layoutDirectionApplier) {
public RootPresenter(StackAnimator animator, LayoutDirectionApplier layoutDirectionApplier) {
this.animator = animator;
this.layoutDirectionApplier = layoutDirectionApplier;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.Size;
import androidx.core.util.Pair;

@SuppressWarnings("WeakerAccess")
Expand Down Expand Up @@ -153,6 +154,10 @@ public static <T> T last(@Nullable List<T> items) {
return CollectionUtils.isNullOrEmpty(items) ? null : items.get(items.size() - 1);
}

public static <T> T requireLast(@Size(min = 1) List<T> items) {
return items.get(items.size() - 1);
}

public static <T> T removeLast(@NonNull List<T> items) {
return items.remove(items.size() - 1);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ public void setDefaultOptions(Options defaultOptions) {
}

@Override
public void onViewAppeared() {
super.onViewAppeared();
public void onViewWillAppear() {
super.onViewWillAppear();
childRegistry.onViewAppeared(this);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ public class ComponentViewController extends ChildController<ComponentLayout> {
private final String componentName;
private ComponentPresenter presenter;
private final ReactViewCreator viewCreator;
private enum VisibilityState { Appear, Disappear }
private VisibilityState lastVisibilityState = VisibilityState.Disappear;

public ComponentViewController(final Activity activity,
final ChildControllersRegistry childRegistry,
Expand Down Expand Up @@ -57,13 +59,15 @@ public ScrollEventListener getScrollEventListener() {
}

@Override
public void onViewAppeared() {
super.onViewAppeared();
if (view != null) view.sendComponentStart();
public void onViewDidAppear() {
super.onViewDidAppear();
if (view != null && lastVisibilityState == VisibilityState.Disappear) view.sendComponentStart();
lastVisibilityState = VisibilityState.Appear;
}

@Override
public void onViewDisappear() {
lastVisibilityState = VisibilityState.Disappear;
if (view != null) view.sendComponentStop();
super.onViewDisappear();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ public void setDefaultOptions(Options defaultOptions) {
forEach(getChildControllers(), child -> child.setDefaultOptions(defaultOptions));
}

@Override
public void onViewDidAppear() {
getCurrentChild().onViewDidAppear();
}

@Override
@CheckResult
public Options resolveCurrentOptions() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class TitleBarButtonController(activity: Activity,
get() = button.intId

@SuppressLint("MissingSuperCall")
override fun onViewAppeared() {
override fun onViewWillAppear() {
getView()!!.sendComponentStart(ComponentType.Button)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ public TitleBarReactViewController(Activity activity, TitleBarReactViewCreator r
}

@Override
public void onViewAppeared() {
super.onViewAppeared();
public void onViewWillAppear() {
super.onViewWillAppear();
if (!isDestroyed()) {
runOnPreDraw(view -> view.setLayoutParams(view.getLayoutParams()));
getView().sendComponentStart(ComponentType.Title);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,12 +231,8 @@ public boolean containsComponent(Component component) {
return getView().equals(component);
}

public void onViewWillAppear() {

}

@CallSuper
public void onViewAppeared() {
public void onViewWillAppear() {
isShown = true;
applyOptions(options);
performOnParentController(parentController -> {
Expand All @@ -252,6 +248,10 @@ public void onViewAppeared() {
}
}

public void onViewDidAppear() {

}

public void onViewWillDisappear() {

}
Expand Down Expand Up @@ -291,7 +291,7 @@ public void onGlobalLayout() {
if (!isShown && isViewShown()) {
if (!viewVisibilityListener.onViewAppeared(view)) {
isShown = true;
onViewAppeared();
onViewWillAppear();
}
} else if (isShown && !isViewShown()) {
if (!viewVisibilityListener.onViewDisappear(view)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ public void selectTab(final int newIndex) {
getCurrentView().setVisibility(View.INVISIBLE);
bottomTabs.setCurrentItem(newIndex, false);
getCurrentView().setVisibility(View.VISIBLE);
getCurrentChild().onViewDidAppear();
}

@NonNull
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ public void sendOnNavigationButtonPressed(String buttonId) {
}

@Override
public void onViewAppeared() {
super.onViewAppeared();
public void onViewWillAppear() {
super.onViewWillAppear();
emitter.emitComponentDidAppear(getId(), externalComponent.name.get(), ComponentType.Component);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ public void onAnimationEnd(Animator animation) {
}

private void onShowModalEnd(ViewController toAdd, @Nullable ViewController toRemove, CommandListener listener) {
if (toRemove != null && toAdd.options.modal.presentationStyle != ModalPresentationStyle.OverCurrentContext) {
toAdd.onViewDidAppear();
if (toRemove != null && toAdd.resolveCurrentOptions(defaultOptions).modal.presentationStyle != ModalPresentationStyle.OverCurrentContext) {
toRemove.detachView();
}
listener.onSuccess(toAdd.getId());
Expand All @@ -92,7 +93,10 @@ void dismissModal(ViewController toDismiss, @Nullable ViewController toAdd, View
listener.onError("Can not dismiss modal before activity is created");
return;
}
if (toAdd != null) toAdd.attachView(toAdd == root ? rootLayout : modalsLayout, 0);
if (toAdd != null) {
toAdd.attachView(toAdd == root ? rootLayout : modalsLayout, 0);
toAdd.onViewDidAppear();
}
Options options = toDismiss.resolveCurrentOptions(defaultOptions);
if (options.animations.dismissModal.enabled.isTrueOrUndefined()) {
animator.dismiss(toDismiss.getView(), options.animations.dismissModal, new AnimatorListenerAdapter() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,20 +62,19 @@ public boolean dismissModal(String componentId, @Nullable ViewController root, C
boolean isDismissingTopModal = isTop(toDismiss);
modals.remove(toDismiss);
@Nullable ViewController toAdd = isEmpty() ? root : isDismissingTopModal ? get(size() - 1) : null;
CommandListenerAdapter onDismiss = new CommandListenerAdapter(listener) {
@Override
public void onSuccess(String childId) {
eventEmitter.emitModalDismissed(componentId, toDismiss.getCurrentComponentName(), 1);
super.onSuccess(componentId);
}
};
if (isDismissingTopModal) {
if (toAdd == null) {
listener.onError("Could not dismiss modal");
return false;
}
}
presenter.dismissModal(toDismiss, toAdd, root, onDismiss);
presenter.dismissModal(toDismiss, toAdd, root, new CommandListenerAdapter(listener) {
@Override
public void onSuccess(String childId) {
eventEmitter.emitModalDismissed(componentId, toDismiss.getCurrentComponentName(), 1);
super.onSuccess(componentId);
}
});
return true;
} else {
listener.onError("Nothing to dismiss");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ public void setRoot(final ViewController viewController, CommandListener command
rootPresenter.setRoot(root, defaultOptions, new CommandListenerAdapter(commandListener) {
@Override
public void onSuccess(String childId) {
root.onViewDidAppear();
if (removeSplashView) contentLayout.removeViewAt(0);
destroyPreviousRoot();
super.onSuccess(childId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,8 @@ public void mergeChildOptions(Options options, ViewController child) {
}

@Override
public void onViewAppeared() {
super.onViewAppeared();
public void onViewWillAppear() {
super.onViewWillAppear();
if (left != null) left.performOnView(view -> ((View) view).requestLayout());
if (right != null) right.performOnView(view -> ((View) view).requestLayout());
}
Expand Down Expand Up @@ -205,8 +205,10 @@ private Options getOptionsWithVisibility(boolean isLeft, boolean visible ) {
}

private void dispatchSideMenuVisibilityEvents(ViewController drawer, float prevOffset, float offset) {
if (prevOffset == 0 && offset > 0) {
drawer.onViewAppeared();
if (prevOffset < 1 && offset == 1) {
drawer.onViewDidAppear();
} else if (prevOffset == 0 && offset > 0) {
drawer.onViewWillAppear();
} else if (prevOffset > 0 && offset == 0) {
drawer.onViewDisappear();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import android.view.View;
import android.view.ViewGroup;

import com.reactnativenavigation.anim.NavigationAnimator;
import com.reactnativenavigation.anim.StackAnimator;
import com.reactnativenavigation.parse.NestedAnimationsOptions;
import com.reactnativenavigation.parse.Options;
import com.reactnativenavigation.presentation.FabPresenter;
Expand Down Expand Up @@ -34,6 +34,7 @@

import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
import androidx.annotation.Size;
import androidx.annotation.VisibleForTesting;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.viewpager.widget.ViewPager;
Expand All @@ -46,14 +47,14 @@
public class StackController extends ParentController<StackLayout> {

private IdStack<ViewController> stack = new IdStack<>();
private final NavigationAnimator animator;
private final StackAnimator animator;
private final EventEmitter eventEmitter;
private TopBarController topBarController;
private BackButtonHelper backButtonHelper;
private final StackPresenter presenter;
private final FabPresenter fabPresenter;

public StackController(Activity activity, List<ViewController> children, ChildControllersRegistry childRegistry, EventEmitter eventEmitter, TopBarController topBarController, NavigationAnimator animator, String id, Options initialOptions, BackButtonHelper backButtonHelper, StackPresenter stackPresenter, Presenter presenter, FabPresenter fabPresenter) {
public StackController(Activity activity, List<ViewController> children, ChildControllersRegistry childRegistry, EventEmitter eventEmitter, TopBarController topBarController, StackAnimator animator, String id, Options initialOptions, BackButtonHelper backButtonHelper, StackPresenter stackPresenter, Presenter presenter, FabPresenter fabPresenter) {
super(activity, childRegistry, id, presenter, initialOptions);
this.eventEmitter = eventEmitter;
this.topBarController = topBarController;
Expand Down Expand Up @@ -167,6 +168,7 @@ public void push(ViewController child, CommandListener listener) {
if (animation.enabled.isTrueOrUndefined()) {
animator.push(child, toRemove, resolvedOptions, () -> onPushAnimationComplete(child, toRemove, listener));
} else {
child.onViewDidAppear();
getView().removeView(toRemove.getView());
listener.onSuccess(child.getId());
}
Expand All @@ -176,6 +178,7 @@ public void push(ViewController child, CommandListener listener) {
}

private void onPushAnimationComplete(ViewController toAdd, ViewController toRemove, CommandListener listener) {
toAdd.onViewDidAppear();
if (!peek().equals(toRemove)) getView().removeView(toRemove.getView());
listener.onSuccess(toAdd.getId());
}
Expand All @@ -186,13 +189,13 @@ private void addChildToStack(ViewController child, Options resolvedOptions) {
getView().addView(child.getView(), getView().getChildCount() - 1, matchParentWithBehaviour(new StackBehaviour(this)));
}

public void setRoot(List<ViewController> children, CommandListener listener) {
public void setRoot(@Size(min = 1) List<ViewController> children, CommandListener listener) {
animator.cancelPushAnimations();
final ViewController toRemove = stack.peek();
IdStack stackToDestroy = stack;
stack = new IdStack<>();

ViewController child = last(children);
ViewController child = requireLast(children);
if (children.size() == 1) {
backButtonHelper.clear(child);
} else {
Expand All @@ -207,6 +210,7 @@ public void setRoot(List<ViewController> children, CommandListener listener) {
CommandListener listenerAdapter = new CommandListenerAdapter() {
@Override
public void onSuccess(String childId) {
child.onViewDidAppear();
destroyStack(stackToDestroy);
if (children.size() > 1) {
for (int i = 0; i < children.size() - 1; i++) {
Expand Down Expand Up @@ -261,7 +265,6 @@ public void pop(Options mergeOptions, CommandListener listener) {
final ViewController appearing = stack.peek();

disappearing.onViewWillDisappear();
appearing.onViewWillAppear();

ViewGroup appearingView = appearing.getView();
if (appearingView.getLayoutParams() == null) {
Expand All @@ -272,13 +275,14 @@ public void pop(Options mergeOptions, CommandListener listener) {
}
presenter.onChildWillAppear(this, appearing, disappearing);
if (disappearingOptions.animations.pop.enabled.isTrueOrUndefined()) {
animator.pop(disappearing.getView(), disappearingOptions.animations.pop, () -> finishPopping(disappearing, listener));
animator.pop(disappearing.getView(), disappearingOptions.animations.pop, () -> finishPopping(appearing, disappearing, listener));
} else {
finishPopping(disappearing, listener);
finishPopping(appearing, disappearing, listener);
}
}

private void finishPopping(ViewController disappearing, CommandListener listener) {
private void finishPopping(ViewController appearing, ViewController disappearing, CommandListener listener) {
appearing.onViewDidAppear();
disappearing.destroy();
listener.onSuccess(disappearing.getId());
eventEmitter.emitScreenPoppedEvent(disappearing.getId());
Expand Down
Loading

0 comments on commit b9310bf

Please sign in to comment.