Browse Source

Cancel in-flight push animation on pop (#4842)

When popToRoot or popTo where called while a screen was pushed, at the end of the push animation RNN tried to destroy an already destroyed ViewController.
Related to #3767
Guy Carmeli 5 years ago
parent
commit
47b7d2c7c5
No account linked to committer's email address

+ 16
- 1
lib/android/app/src/main/java/com/reactnativenavigation/anim/NavigationAnimator.java View File

4
 import android.animation.AnimatorListenerAdapter;
4
 import android.animation.AnimatorListenerAdapter;
5
 import android.animation.AnimatorSet;
5
 import android.animation.AnimatorSet;
6
 import android.content.Context;
6
 import android.content.Context;
7
+import android.support.annotation.RestrictTo;
7
 import android.view.View;
8
 import android.view.View;
8
 import android.view.ViewGroup;
9
 import android.view.ViewGroup;
9
 
10
 
19
 import java.util.List;
20
 import java.util.List;
20
 import java.util.Map;
21
 import java.util.Map;
21
 
22
 
22
-import static com.reactnativenavigation.utils.CollectionUtils.merge;
23
+import static com.reactnativenavigation.utils.CollectionUtils.*;
23
 
24
 
24
 @SuppressWarnings("ResourceType")
25
 @SuppressWarnings("ResourceType")
25
 public class NavigationAnimator extends BaseAnimator {
26
 public class NavigationAnimator extends BaseAnimator {
108
         });
109
         });
109
         set.start();
110
         set.start();
110
     }
111
     }
112
+
113
+    public void cancelPushAnimations() {
114
+        for (View view : runningPushAnimations.keySet()) {
115
+            runningPushAnimations.get(view).cancel();
116
+            runningPushAnimations.remove(view);
117
+        }
118
+    }
119
+
120
+    @RestrictTo(RestrictTo.Scope.TESTS)
121
+    public void endPushAnimation(View view) {
122
+        if (runningPushAnimations.containsKey(view)) {
123
+            runningPushAnimations.get(view).end();
124
+        }
125
+    }
111
 }
126
 }

+ 2
- 1
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/StackController.java View File

275
             return;
275
             return;
276
         }
276
         }
277
 
277
 
278
-
278
+        animator.cancelPushAnimations();
279
         String currentControlId;
279
         String currentControlId;
280
         for (int i = stack.size() - 2; i >= 0; i--) {
280
         for (int i = stack.size() - 2; i >= 0; i--) {
281
             currentControlId = stack.get(i).getId();
281
             currentControlId = stack.get(i).getId();
297
             return;
297
             return;
298
         }
298
         }
299
 
299
 
300
+        animator.cancelPushAnimations();
300
         Iterator<String> iterator = stack.iterator();
301
         Iterator<String> iterator = stack.iterator();
301
         iterator.next();
302
         iterator.next();
302
         while (stack.size() > 2) {
303
         while (stack.size() > 2) {

+ 39
- 3
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/stack/StackControllerTest.java View File

42
 import org.json.JSONException;
42
 import org.json.JSONException;
43
 import org.json.JSONObject;
43
 import org.json.JSONObject;
44
 import org.junit.Test;
44
 import org.junit.Test;
45
-import org.mockito.ArgumentCaptor;
46
-import org.mockito.InOrder;
47
-import org.mockito.Mockito;
45
+import org.mockito.*;
48
 
46
 
49
 import java.util.ArrayList;
47
 import java.util.ArrayList;
50
 import java.util.Arrays;
48
 import java.util.Arrays;
297
         });
295
         });
298
     }
296
     }
299
 
297
 
298
+    @Test
299
+    public void pop_screenCurrentlyBeingPushedIsPopped() {
300
+        disablePushAnimation(child1, child2);
301
+        uut.push(child1, Mockito.mock(CommandListenerAdapter.class));
302
+        uut.push(child2, Mockito.mock(CommandListenerAdapter.class));
303
+
304
+        uut.push(child3, Mockito.mock(CommandListenerAdapter.class));
305
+        uut.pop(Options.EMPTY, Mockito.mock(CommandListenerAdapter.class));
306
+        assertThat(uut.size()).isEqualTo(2);
307
+        assertContainsOnlyId(child1.getId(), child2.getId());
308
+    }
309
+
300
     @Test
310
     @Test
301
     public void pop_appliesOptionsAfterPop() {
311
     public void pop_appliesOptionsAfterPop() {
302
         uut.push(child1, new CommandListenerAdapter());
312
         uut.push(child1, new CommandListenerAdapter());
622
         });
632
         });
623
     }
633
     }
624
 
634
 
635
+    @Test
636
+    public void popTo_pushAnimationIsCancelled() {
637
+        disablePushAnimation(child1, child2);
638
+        uut.push(child1, Mockito.mock(CommandListenerAdapter.class));
639
+        uut.push(child2, Mockito.mock(CommandListenerAdapter.class));
640
+
641
+        ViewGroup pushed = child3.getView();
642
+        uut.push(child3, Mockito.mock(CommandListenerAdapter.class));
643
+        uut.popTo(child1, Options.EMPTY, Mockito.mock(CommandListenerAdapter.class));
644
+        animator.endPushAnimation(pushed);
645
+        assertContainsOnlyId(child1.getId());
646
+    }
647
+
625
     @Test
648
     @Test
626
     public void popToRoot_PopsEverythingAboveFirstController() {
649
     public void popToRoot_PopsEverythingAboveFirstController() {
627
         child1.options.animations.push.enabled = new Bool(false);
650
         child1.options.animations.push.enabled = new Bool(false);
707
         verify(child1, times(0)).mergeOptions(mergeOptions);
730
         verify(child1, times(0)).mergeOptions(mergeOptions);
708
     }
731
     }
709
 
732
 
733
+    @Test
734
+    public void popToRoot_screenPushedBeforePopAnimationCompletesIsPopped() {
735
+        disablePushAnimation(child1, child2);
736
+        uut.push(child1, Mockito.mock(CommandListenerAdapter.class));
737
+        uut.push(child2, Mockito.mock(CommandListenerAdapter.class));
738
+
739
+        ViewGroup pushed = child3.getView();
740
+        uut.push(child3, Mockito.mock(CommandListenerAdapter.class));
741
+        uut.popToRoot(Options.EMPTY, Mockito.mock(CommandListenerAdapter.class));
742
+        animator.endPushAnimation(pushed);
743
+        assertContainsOnlyId(child1.getId());
744
+    }
745
+
710
     @Test
746
     @Test
711
     public void findControllerById_ReturnsSelfOrChildrenById() {
747
     public void findControllerById_ReturnsSelfOrChildrenById() {
712
         assertThat(uut.findController("123")).isNull();
748
         assertThat(uut.findController("123")).isNull();