Browse Source

Unmount react views when bundle load completes (#3487)

Unmount react views when bundle load completes

Users providing their own ReactNativeHost implementations should implement BundleDownloadListenerProvider
and make sure they set DevBundleDownloadListener when ReactInstanceManager.
Guy Carmeli 6 years ago
parent
commit
50fd77ec82
No account linked to committer's email address

+ 11
- 9
lib/android/app/src/main/java/com/reactnativenavigation/NavigationActivity.java View File

@@ -1,27 +1,26 @@
1 1
 package com.reactnativenavigation;
2 2
 
3 3
 import android.annotation.TargetApi;
4
-import android.support.annotation.NonNull;
5
-
6 4
 import android.content.Intent;
7
-import android.os.Bundle;
8 5
 import android.os.Build;
6
+import android.os.Bundle;
7
+import android.support.annotation.NonNull;
9 8
 import android.support.annotation.Nullable;
10 9
 import android.support.v7.app.AppCompatActivity;
11 10
 import android.view.KeyEvent;
12 11
 import android.view.View;
13 12
 
14 13
 import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
14
+import com.facebook.react.modules.core.PermissionAwareActivity;
15
+import com.facebook.react.modules.core.PermissionListener;
15 16
 import com.reactnativenavigation.presentation.OverlayManager;
17
+import com.reactnativenavigation.react.JsDevReloadHandler;
16 18
 import com.reactnativenavigation.react.ReactGateway;
17 19
 import com.reactnativenavigation.utils.CommandListenerAdapter;
18 20
 import com.reactnativenavigation.viewcontrollers.ChildControllersRegistry;
19 21
 import com.reactnativenavigation.viewcontrollers.Navigator;
20 22
 
21
-import com.facebook.react.modules.core.PermissionAwareActivity;
22
-import com.facebook.react.modules.core.PermissionListener;
23
-
24
-public class NavigationActivity extends AppCompatActivity implements DefaultHardwareBackBtnHandler, PermissionAwareActivity {
23
+public class NavigationActivity extends AppCompatActivity implements DefaultHardwareBackBtnHandler, PermissionAwareActivity, JsDevReloadHandler.ReloadListener {
25 24
     @Nullable
26 25
     private PermissionListener mPermissionListener;
27 26
     
@@ -32,7 +31,6 @@ public class NavigationActivity extends AppCompatActivity implements DefaultHard
32 31
         super.onCreate(savedInstanceState);
33 32
         navigator = new Navigator(this, new ChildControllersRegistry(), new OverlayManager());
34 33
         getReactGateway().onActivityCreated(this);
35
-        getReactGateway().addReloadListener(navigator);
36 34
         navigator.getView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
37 35
         setContentView(navigator.getView());
38 36
     }
@@ -53,7 +51,6 @@ public class NavigationActivity extends AppCompatActivity implements DefaultHard
53 51
     protected void onDestroy() {
54 52
         super.onDestroy();
55 53
         navigator.destroy();
56
-        getReactGateway().removeReloadListener(navigator);
57 54
         getReactGateway().onActivityDestroyed(this);
58 55
     }
59 56
 
@@ -105,4 +102,9 @@ public class NavigationActivity extends AppCompatActivity implements DefaultHard
105 102
             mPermissionListener = null;
106 103
         }
107 104
     }
105
+
106
+    @Override
107
+    public void onReload() {
108
+        navigator.destroyViews();
109
+    }
108 110
 }

+ 7
- 0
lib/android/app/src/main/java/com/reactnativenavigation/react/BundleDownloadListenerProvider.java View File

@@ -0,0 +1,7 @@
1
+package com.reactnativenavigation.react;
2
+
3
+import com.facebook.react.devsupport.interfaces.DevBundleDownloadListener;
4
+
5
+public interface BundleDownloadListenerProvider {
6
+    void setBundleLoaderListener(DevBundleDownloadListener listener);
7
+}

+ 22
- 0
lib/android/app/src/main/java/com/reactnativenavigation/react/DevBundleDownloadListenerAdapter.java View File

@@ -0,0 +1,22 @@
1
+package com.reactnativenavigation.react;
2
+
3
+import com.facebook.react.devsupport.interfaces.DevBundleDownloadListener;
4
+
5
+import javax.annotation.Nullable;
6
+
7
+public class DevBundleDownloadListenerAdapter implements DevBundleDownloadListener {
8
+    @Override
9
+    public void onSuccess() {
10
+
11
+    }
12
+
13
+    @Override
14
+    public void onProgress(@Nullable String status, @Nullable Integer done, @Nullable Integer total) {
15
+
16
+    }
17
+
18
+    @Override
19
+    public void onFailure(Exception cause) {
20
+
21
+    }
22
+}

+ 34
- 13
lib/android/app/src/main/java/com/reactnativenavigation/react/JsDevReloadHandler.java View File

@@ -7,29 +7,50 @@ import android.content.Intent;
7 7
 import android.content.IntentFilter;
8 8
 import android.view.KeyEvent;
9 9
 
10
-import com.facebook.react.ReactInstanceManager;
10
+import com.facebook.react.devsupport.interfaces.DevBundleDownloadListener;
11
+import com.facebook.react.devsupport.interfaces.DevSupportManager;
12
+import com.reactnativenavigation.utils.UiUtils;
13
+
14
+import javax.annotation.Nullable;
15
+
16
+public class JsDevReloadHandler implements DevBundleDownloadListener {
17
+    private static final String RELOAD_BROADCAST = "com.reactnativenavigation.broadcast.RELOAD";
11 18
 
12
-public class JsDevReloadHandler {
13 19
     public interface ReloadListener {
14 20
         void onReload();
15 21
     }
16 22
 
17
-	private static final String RELOAD_BROADCAST = "com.reactnativenavigation.broadcast.RELOAD";
18 23
 	private final BroadcastReceiver reloadReceiver = new BroadcastReceiver() {
19 24
 		@Override
20 25
 		public void onReceive(final Context context, final Intent intent) {
21 26
 			reloadReactNative();
22 27
 		}
23 28
 	};
24
-	private final ReactInstanceManager reactInstanceManager;
25
-	private long firstRTimestamp = 0;
26
-    private ReloadListener reloadListener;
29
+    private final DevSupportManager devSupportManager;
27 30
 
28
-    JsDevReloadHandler(final ReactInstanceManager reactInstanceManager) {
29
-		this.reactInstanceManager = reactInstanceManager;
30
-	}
31
+    private long firstRTimestamp = 0;
32
+    private ReloadListener reloadListener = () -> {};
33
+
34
+    JsDevReloadHandler(DevSupportManager devSupportManager) {
35
+        this.devSupportManager = devSupportManager;
36
+    }
37
+
38
+    @Override
39
+    public void onSuccess() {
40
+        UiUtils.runOnMainThread(reloadListener::onReload);
41
+    }
42
+
43
+    @Override
44
+    public void onProgress(@Nullable String status, @Nullable Integer done, @Nullable Integer total) {
45
+
46
+    }
47
+
48
+    @Override
49
+    public void onFailure(Exception cause) {
50
+
51
+    }
31 52
 
32
-    public void addReloadListener(ReloadListener listener) {
53
+    public void setReloadListener(ReloadListener listener) {
33 54
         reloadListener = listener;
34 55
     }
35 56
 
@@ -48,12 +69,12 @@ public class JsDevReloadHandler {
48 69
 	}
49 70
 
50 71
 	public boolean onKeyUp(int keyCode) {
51
-		if (!reactInstanceManager.getDevSupportManager().getDevSupportEnabled()) {
72
+		if (!devSupportManager.getDevSupportEnabled()) {
52 73
 			return false;
53 74
 		}
54 75
 
55 76
 		if (keyCode == KeyEvent.KEYCODE_MENU) {
56
-			reactInstanceManager.getDevSupportManager().showDevOptionsDialog();
77
+			devSupportManager.showDevOptionsDialog();
57 78
 			return true;
58 79
 		}
59 80
 
@@ -73,6 +94,6 @@ public class JsDevReloadHandler {
73 94
 
74 95
 	private void reloadReactNative() {
75 96
         reloadListener.onReload();
76
-		reactInstanceManager.getDevSupportManager().handleReloadJS();
97
+		devSupportManager.handleReloadJS();
77 98
 	}
78 99
 }

+ 78
- 26
lib/android/app/src/main/java/com/reactnativenavigation/react/NavigationReactNativeHost.java View File

@@ -1,9 +1,16 @@
1 1
 package com.reactnativenavigation.react;
2 2
 
3 3
 import android.app.Application;
4
+import android.support.annotation.NonNull;
5
+import android.support.annotation.Nullable;
4 6
 
7
+import com.facebook.infer.annotation.Assertions;
8
+import com.facebook.react.ReactInstanceManager;
9
+import com.facebook.react.ReactInstanceManagerBuilder;
5 10
 import com.facebook.react.ReactNativeHost;
6 11
 import com.facebook.react.ReactPackage;
12
+import com.facebook.react.common.LifecycleState;
13
+import com.facebook.react.devsupport.interfaces.DevBundleDownloadListener;
7 14
 import com.facebook.react.shell.MainReactPackage;
8 15
 import com.reactnativenavigation.NavigationApplication;
9 16
 
@@ -14,43 +21,88 @@ import java.util.List;
14 21
  * Default implementation of {@link ReactNativeHost} that includes {@link NavigationPackage}
15 22
  * and user-defined additional packages.
16 23
  */
17
-public class NavigationReactNativeHost extends ReactNativeHost {
24
+public class NavigationReactNativeHost extends ReactNativeHost implements BundleDownloadListenerProvider {
25
+
26
+    private final boolean isDebug;
27
+    private final List<ReactPackage> additionalReactPackages;
28
+    private @Nullable DevBundleDownloadListener bundleListener;
29
+    private final DevBundleDownloadListener bundleListenerMediator = new DevBundleDownloadListenerAdapter() {
30
+        @Override
31
+        public void onSuccess() {
32
+            if (bundleListener != null) {
33
+                bundleListener.onSuccess();
34
+            }
35
+        }
36
+    };
18 37
 
19
-	private final boolean isDebug;
20
-	private final List<ReactPackage> additionalReactPackages;
21 38
 
22 39
     public NavigationReactNativeHost(NavigationApplication application) {
23 40
         this(application, application.isDebug(), application.createAdditionalReactPackages());
24 41
     }
25 42
 
26
-	@SuppressWarnings("WeakerAccess")
43
+    @SuppressWarnings("WeakerAccess")
27 44
     public NavigationReactNativeHost(Application application, boolean isDebug, final List<ReactPackage> additionalReactPackages) {
28
-		super(application);
29
-		this.isDebug = isDebug;
30
-		this.additionalReactPackages = additionalReactPackages;
31
-	}
45
+        super(application);
46
+        this.isDebug = isDebug;
47
+        this.additionalReactPackages = additionalReactPackages;
48
+    }
32 49
 
33
-	@Override
34
-	public boolean getUseDeveloperSupport() {
35
-		return isDebug;
36
-	}
50
+    @Override
51
+    public void setBundleLoaderListener(DevBundleDownloadListener listener) {
52
+        bundleListener = listener;
53
+    }
37 54
 
38 55
     @Override
39
-	protected List<ReactPackage> getPackages() {
40
-		List<ReactPackage> packages = new ArrayList<>();
41
-		boolean hasMainReactPackage = false;
42
-		packages.add(new NavigationPackage(this));
43
-		if (additionalReactPackages != null) {
44
-			for (ReactPackage p : additionalReactPackages) {
45
-				if (!(p instanceof NavigationPackage)) {
46
-					packages.add(p);
47
-				}
48
-				if (p instanceof MainReactPackage) hasMainReactPackage = true;
49
-			}
50
-		}
56
+    public boolean getUseDeveloperSupport() {
57
+        return isDebug;
58
+    }
59
+
60
+    @Override
61
+    protected List<ReactPackage> getPackages() {
62
+        List<ReactPackage> packages = new ArrayList<>();
63
+        boolean hasMainReactPackage = false;
64
+        packages.add(new NavigationPackage(this));
65
+        if (additionalReactPackages != null) {
66
+            for (ReactPackage p : additionalReactPackages) {
67
+                if (!(p instanceof NavigationPackage)) {
68
+                    packages.add(p);
69
+                }
70
+                if (p instanceof MainReactPackage) hasMainReactPackage = true;
71
+            }
72
+        }
51 73
         if (!hasMainReactPackage) {
52 74
             packages.add(new MainReactPackage());
53 75
         }
54
-		return packages;
55
-	}
76
+        return packages;
77
+    }
78
+
79
+    protected ReactInstanceManager createReactInstanceManager() {
80
+        ReactInstanceManagerBuilder builder = ReactInstanceManager.builder()
81
+                .setApplication(getApplication())
82
+                .setJSMainModulePath(getJSMainModuleName())
83
+                .setUseDeveloperSupport(getUseDeveloperSupport())
84
+                .setRedBoxHandler(getRedBoxHandler())
85
+                .setJavaScriptExecutorFactory(getJavaScriptExecutorFactory())
86
+                .setUIImplementationProvider(getUIImplementationProvider())
87
+                .setInitialLifecycleState(LifecycleState.BEFORE_CREATE)
88
+                .setDevBundleDownloadListener(getDevBundleDownloadListener());
89
+
90
+        for (ReactPackage reactPackage : getPackages()) {
91
+            builder.addPackage(reactPackage);
92
+        }
93
+
94
+        String jsBundleFile = getJSBundleFile();
95
+        if (jsBundleFile != null) {
96
+            builder.setJSBundleFile(jsBundleFile);
97
+        } else {
98
+            builder.setBundleAssetName(Assertions.assertNotNull(getBundleAssetName()));
99
+        }
100
+        return builder.build();
101
+    }
102
+
103
+    @SuppressWarnings("WeakerAccess")
104
+    @NonNull
105
+    protected DevBundleDownloadListener getDevBundleDownloadListener() {
106
+        return bundleListenerMediator;
107
+    }
56 108
 }

+ 16
- 18
lib/android/app/src/main/java/com/reactnativenavigation/react/ReactGateway.java View File

@@ -13,27 +13,32 @@ import java.util.List;
13 13
 
14 14
 public class ReactGateway {
15 15
 
16
-	private final ReactNativeHost reactNativeHost;
16
+	private final ReactNativeHost host;
17 17
 	private final NavigationReactInitializer initializer;
18 18
 	private final JsDevReloadHandler jsDevReloadHandler;
19 19
 
20
-	public ReactGateway(final Application application, final boolean isDebug, final List<ReactPackage> additionalReactPackages) {
20
+    @SuppressWarnings("unused")
21
+    public ReactGateway(final Application application, final boolean isDebug, final List<ReactPackage> additionalReactPackages) {
21 22
 		this(application, isDebug, new NavigationReactNativeHost(application, isDebug, additionalReactPackages));
22 23
 	}
23 24
 
24
-	public ReactGateway(final Application application, final boolean isDebug, final ReactNativeHost reactNativeHost) {
25
-		SoLoader.init(application, false);
26
-		this.reactNativeHost = reactNativeHost;
27
-		initializer = new NavigationReactInitializer(reactNativeHost.getReactInstanceManager(), isDebug);
28
-		jsDevReloadHandler = new JsDevReloadHandler(reactNativeHost.getReactInstanceManager());
25
+	public ReactGateway(final Application application, final boolean isDebug, final ReactNativeHost host) {
26
+        SoLoader.init(application, false);
27
+		this.host = host;
28
+		initializer = new NavigationReactInitializer(host.getReactInstanceManager(), isDebug);
29
+		jsDevReloadHandler = new JsDevReloadHandler(host.getReactInstanceManager().getDevSupportManager());
30
+        if (host instanceof BundleDownloadListenerProvider) {
31
+            ((BundleDownloadListenerProvider) host).setBundleLoaderListener(jsDevReloadHandler);
32
+        }
29 33
 	}
30 34
 
31 35
 	public ReactNativeHost getReactNativeHost() {
32
-		return reactNativeHost;
36
+		return host;
33 37
 	}
34 38
 
35 39
 	public void onActivityCreated(NavigationActivity activity) {
36 40
 		initializer.onActivityCreated(activity);
41
+        jsDevReloadHandler.setReloadListener(activity);
37 42
 	}
38 43
 
39 44
 	public void onActivityResumed(NavigationActivity activity) {
@@ -47,26 +52,19 @@ public class ReactGateway {
47 52
 	}
48 53
 
49 54
 	public void onActivityDestroyed(NavigationActivity activity) {
55
+        jsDevReloadHandler.removeReloadListener(activity);
50 56
 		initializer.onActivityDestroyed(activity);
51 57
 	}
52 58
 
53
-    public void addReloadListener(JsDevReloadHandler.ReloadListener reloadListener) {
54
-	    jsDevReloadHandler.addReloadListener(reloadListener);
55
-    }
56
-
57
-    public void removeReloadListener(JsDevReloadHandler.ReloadListener reloadListener) {
58
-	    jsDevReloadHandler.removeReloadListener(reloadListener);
59
-    }
60
-
61 59
 	public boolean onKeyUp(final int keyCode) {
62 60
 		return jsDevReloadHandler.onKeyUp(keyCode);
63 61
 	}
64 62
 
65 63
     public void onBackPressed() {
66
-	    reactNativeHost.getReactInstanceManager().onBackPressed();
64
+	    host.getReactInstanceManager().onBackPressed();
67 65
     }
68 66
 
69 67
     public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
70
-        reactNativeHost.getReactInstanceManager().onActivityResult(activity, requestCode, resultCode, data);
68
+        host.getReactInstanceManager().onActivityResult(activity, requestCode, resultCode, data);
71 69
     }
72 70
 }

+ 50
- 0
lib/android/app/src/main/java/com/reactnativenavigation/react/ReloadHandler.java View File

@@ -0,0 +1,50 @@
1
+package com.reactnativenavigation.react;
2
+
3
+import com.facebook.react.devsupport.interfaces.DevBundleDownloadListener;
4
+
5
+import javax.annotation.Nullable;
6
+
7
+public class ReloadHandler implements JsDevReloadHandler.ReloadListener, DevBundleDownloadListener {
8
+
9
+    private Runnable onReloadListener = () -> {};
10
+
11
+    public void setOnReloadListener(Runnable onReload) {
12
+        this.onReloadListener = onReload;
13
+    }
14
+
15
+    /**
16
+     * Called on RR and adb reload events
17
+     */
18
+    @Override
19
+    public void onReload() {
20
+        onReloadListener.run();
21
+    }
22
+
23
+    /**
24
+     * Called when the bundle was successfully reloaded
25
+     */
26
+    @Override
27
+    public void onSuccess() {
28
+        onReloadListener.run();
29
+    }
30
+
31
+    /**
32
+     * Bundle progress updates
33
+     */
34
+    @Override
35
+    public void onProgress(@Nullable String status, @Nullable Integer done, @Nullable Integer total) {
36
+
37
+    }
38
+
39
+    /**
40
+     * Bundle load failure
41
+     */
42
+    @Override
43
+    public void onFailure(Exception cause) {
44
+
45
+    }
46
+
47
+    public void destroy() {
48
+        onReloadListener = null;
49
+    }
50
+}

+ 2
- 8
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/Navigator.java View File

@@ -13,7 +13,6 @@ import com.reactnativenavigation.anim.NavigationAnimator;
13 13
 import com.reactnativenavigation.parse.Options;
14 14
 import com.reactnativenavigation.presentation.OptionsPresenter;
15 15
 import com.reactnativenavigation.presentation.OverlayManager;
16
-import com.reactnativenavigation.react.JsDevReloadHandler;
17 16
 import com.reactnativenavigation.utils.CommandListener;
18 17
 import com.reactnativenavigation.utils.CommandListenerAdapter;
19 18
 import com.reactnativenavigation.utils.CompatUtils;
@@ -24,7 +23,7 @@ import com.reactnativenavigation.viewcontrollers.stack.StackController;
24 23
 import java.util.Collection;
25 24
 import java.util.Collections;
26 25
 
27
-public class Navigator extends ParentController implements JsDevReloadHandler.ReloadListener {
26
+public class Navigator extends ParentController {
28 27
 
29 28
     private final ModalStack modalStack;
30 29
     private ViewController root;
@@ -80,18 +79,13 @@ public class Navigator extends ParentController implements JsDevReloadHandler.Re
80 79
         return root;
81 80
     }
82 81
 
83
-    @Override
84
-    public void onReload() {
85
-        destroyViews();
86
-    }
87
-
88 82
     @Override
89 83
     public void destroy() {
90 84
         destroyViews();
91 85
         super.destroy();
92 86
     }
93 87
 
94
-    private void destroyViews() {
88
+    public void destroyViews() {
95 89
         modalStack.dismissAllModals(new CommandListenerAdapter(), root);
96 90
         overlayManager.destroy();
97 91
         destroyRoot();

+ 24
- 0
lib/android/app/src/test/java/com/reactnativenavigation/react/ReloadListenerTest.java View File

@@ -0,0 +1,24 @@
1
+package com.reactnativenavigation.react;
2
+
3
+import com.reactnativenavigation.BaseTest;
4
+
5
+import org.junit.Test;
6
+import org.mockito.Mockito;
7
+
8
+public class ReloadListenerTest extends BaseTest {
9
+    private ReloadHandler uut;
10
+    private Runnable handler;
11
+
12
+    @Override
13
+    public void beforeEach() {
14
+        handler = Mockito.mock(Runnable.class);
15
+        uut = new ReloadHandler();
16
+    }
17
+
18
+    @Test
19
+    public void onSuccess_viewsAreDestroyed() {
20
+        uut.setOnReloadListener(handler);
21
+        uut.onSuccess();
22
+        Mockito.verify(handler).run();
23
+    }
24
+}

+ 0
- 9
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/NavigatorTest.java View File

@@ -508,13 +508,4 @@ public class NavigatorTest extends BaseTest {
508 508
         uut.destroy();
509 509
         assertThat(childRegistry.size()).isZero();
510 510
     }
511
-
512
-    @Test
513
-    public void reload_navigatorIsDestroyedOnReload() {
514
-        StackController spy = spy(parentController);
515
-        uut.setRoot(spy, new CommandListenerAdapter());
516
-        uut.onReload();
517
-        verify(spy, times(1)).destroy();
518
-        verify(overlayManager, times(1)).destroy();
519
-    }
520 511
 }

+ 1
- 1
playground/src/screens/PushedScreen.js View File

@@ -35,7 +35,7 @@ class PushedScreen extends Component {
35 35
 
36 36
   listeners = [];
37 37
 
38
-  componentWillMount() {
38
+  componentDidMount() {
39 39
     this.listeners.push(
40 40
       Navigation.events().registerNativeEventListener((name, params) => {
41 41
         if (name === 'previewContext') {