Browse Source

Unmount screens before handling bundle reload

When ever bundle was reloaded, all currently registered screens
where recreated since they were still attached to the react context.
This commit adds, in a very hacky way, means to respond to bundle
reload events.
Guy Carmeli 8 years ago
parent
commit
34f09ff4dd

+ 13
- 0
android/app/src/main/java/com/reactnativenavigation/activities/BaseReactActivity.java View File

1
 package com.reactnativenavigation.activities;
1
 package com.reactnativenavigation.activities;
2
 
2
 
3
+import android.content.BroadcastReceiver;
4
+import android.content.Context;
3
 import android.content.Intent;
5
 import android.content.Intent;
6
+import android.content.IntentFilter;
4
 import android.content.res.Configuration;
7
 import android.content.res.Configuration;
5
 import android.os.Build;
8
 import android.os.Build;
6
 import android.os.Bundle;
9
 import android.os.Bundle;
26
 import com.facebook.react.bridge.ReadableMap;
29
 import com.facebook.react.bridge.ReadableMap;
27
 import com.facebook.react.bridge.WritableMap;
30
 import com.facebook.react.bridge.WritableMap;
28
 import com.facebook.react.common.ReactConstants;
31
 import com.facebook.react.common.ReactConstants;
32
+import com.facebook.react.devsupport.DevServerHelper;
29
 import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
33
 import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
30
 import com.facebook.react.shell.MainReactPackage;
34
 import com.facebook.react.shell.MainReactPackage;
31
 import com.reactnativenavigation.BuildConfig;
35
 import com.reactnativenavigation.BuildConfig;
367
         return super.onKeyUp(keyCode, event);
371
         return super.onKeyUp(keyCode, event);
368
     }
372
     }
369
 
373
 
374
+    /**
375
+     * Called after bundle was reloaded. This is a good chance to clean up previously connected react views.
376
+     */
377
+    public void onJSBundleReloaded() {
378
+        removeAllReactViews();
379
+    }
380
+
381
+    protected abstract void removeAllReactViews();
382
+
370
     @Override
383
     @Override
371
     public void onBackPressed() {
384
     public void onBackPressed() {
372
         if (getScreenStackSize() > 1) {
385
         if (getScreenStackSize() > 1) {

+ 7
- 0
android/app/src/main/java/com/reactnativenavigation/activities/BottomTabActivity.java View File

267
         }
267
         }
268
         this.onTabSelected(0, false);
268
         this.onTabSelected(0, false);
269
     }
269
     }
270
+
271
+    @Override
272
+    protected void removeAllReactViews() {
273
+        for (ScreenStack screenStack : mScreenStacks) {
274
+            screenStack.removeAllReactViews();
275
+        }
276
+    }
270
 }
277
 }

+ 4
- 0
android/app/src/main/java/com/reactnativenavigation/activities/RootActivity.java View File

45
         return 0;
45
         return 0;
46
     }
46
     }
47
 
47
 
48
+    @Override
49
+    public void removeAllReactViews() {
50
+
51
+    }
48
 }
52
 }

+ 5
- 0
android/app/src/main/java/com/reactnativenavigation/activities/SingleScreenActivity.java View File

103
     public int getScreenStackSize() {
103
     public int getScreenStackSize() {
104
         return mScreenStack.getStackSize();
104
         return mScreenStack.getStackSize();
105
     }
105
     }
106
+
107
+    @Override
108
+    protected void removeAllReactViews() {
109
+        mScreenStack.removeAllReactViews();
110
+    }
106
 }
111
 }

+ 5
- 1
android/app/src/main/java/com/reactnativenavigation/activities/TabActivity.java View File

1
 package com.reactnativenavigation.activities;
1
 package com.reactnativenavigation.activities;
2
 
2
 
3
-import android.support.design.widget.CoordinatorLayout;
4
 import android.support.v4.view.ViewPager;
3
 import android.support.v4.view.ViewPager;
5
 import android.view.Menu;
4
 import android.view.Menu;
6
 import android.view.ViewGroup;
5
 import android.view.ViewGroup;
69
         return ret;
68
         return ret;
70
     }
69
     }
71
 
70
 
71
+    @Override
72
+    public void removeAllReactViews() {
73
+
74
+    }
75
+
72
     @Override
76
     @Override
73
     public void push(Screen screen) {
77
     public void push(Screen screen) {
74
         super.push(screen);
78
         super.push(screen);

+ 71
- 0
android/app/src/main/java/com/reactnativenavigation/core/RctManager.java View File

1
 package com.reactnativenavigation.core;
1
 package com.reactnativenavigation.core;
2
 
2
 
3
 import android.app.Application;
3
 import android.app.Application;
4
+import android.util.Log;
4
 
5
 
5
 import com.facebook.react.LifecycleState;
6
 import com.facebook.react.LifecycleState;
6
 import com.facebook.react.ReactInstanceManager;
7
 import com.facebook.react.ReactInstanceManager;
7
 import com.facebook.react.ReactPackage;
8
 import com.facebook.react.ReactPackage;
9
+import com.facebook.react.bridge.JavaJSExecutor;
8
 import com.facebook.react.bridge.ReactContext;
10
 import com.facebook.react.bridge.ReactContext;
9
 import com.facebook.react.bridge.ReactContextBaseJavaModule;
11
 import com.facebook.react.bridge.ReactContextBaseJavaModule;
10
 import com.facebook.react.bridge.WritableMap;
12
 import com.facebook.react.bridge.WritableMap;
13
+import com.facebook.react.devsupport.DevSupportManager;
14
+import com.facebook.react.devsupport.ReactInstanceDevCommandsHandler;
11
 import com.reactnativenavigation.activities.BaseReactActivity;
15
 import com.reactnativenavigation.activities.BaseReactActivity;
12
 import com.reactnativenavigation.core.objects.Screen;
16
 import com.reactnativenavigation.core.objects.Screen;
17
+import com.reactnativenavigation.utils.ContextProvider;
18
+import com.reactnativenavigation.utils.ReflectionUtils;
13
 
19
 
14
 import static com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter;
20
 import static com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter;
15
 
21
 
17
  * Created by guyc on 22/02/16.
23
  * Created by guyc on 22/02/16.
18
  */
24
  */
19
 public class RctManager {
25
 public class RctManager {
26
+    private static final String TAG = "RctManager";
20
     private static final String KEY_EVENT_ID = "id";
27
     private static final String KEY_EVENT_ID = "id";
21
     private static RctManager sInstance;
28
     private static RctManager sInstance;
22
 
29
 
68
         }
75
         }
69
 
76
 
70
         mReactManager = builder.build();
77
         mReactManager = builder.build();
78
+        setupDevSupportHandler(mReactManager);
79
+
71
         return mReactManager;
80
         return mReactManager;
72
     }
81
     }
73
 
82
 
83
+    /**
84
+     * Inject our CustomDevCommandsHandler to {@code reactInstanceManager} so we can detect when the bundle was
85
+     * parsed and is about to load.
86
+     * @param reactInstanceManager
87
+     */
88
+    private void setupDevSupportHandler(ReactInstanceManager reactInstanceManager) {
89
+        final ReactInstanceDevCommandsHandler devInterface = (ReactInstanceDevCommandsHandler)
90
+                ReflectionUtils.getDeclaredField(reactInstanceManager, "mDevInterface");
91
+        if (devInterface == null) {
92
+            Log.e(TAG, "Could not get field: mDevInterface");
93
+            return;
94
+        }
95
+
96
+        // Create customDevCommandsHandler
97
+        CustomDevCommandsHandler customDevCommandsHandler = new CustomDevCommandsHandler(devInterface);
98
+        ReflectionUtils.setField(reactInstanceManager, "mDevInterface", customDevCommandsHandler);
99
+
100
+        // Set customDevCommandsHandler in devSupportManager. Fun =).
101
+        DevSupportManager devSupportManager = (DevSupportManager)
102
+                ReflectionUtils.getDeclaredField(reactInstanceManager, "mDevSupportManager");
103
+        ReflectionUtils.setField(devSupportManager, "mReactInstanceCommandsHandler", customDevCommandsHandler);
104
+    }
105
+
74
     public <T extends ReactContextBaseJavaModule> T getNativeModule(Class<T> nativeModuleClass) {
106
     public <T extends ReactContextBaseJavaModule> T getNativeModule(Class<T> nativeModuleClass) {
75
         if (mReactManager == null || mReactManager.getCurrentReactContext() == null) {
107
         if (mReactManager == null || mReactManager.getCurrentReactContext() == null) {
76
             return null;
108
             return null;
113
         mReactManager = null;
145
         mReactManager = null;
114
         sInstance = null;
146
         sInstance = null;
115
     }
147
     }
148
+
149
+    /**
150
+     * Proxy for {@link ReactInstanceDevCommandsHandler} used by {@link DevSupportManager} for requesting React
151
+     * instance recreation. Used to notify {@link BaseReactActivity} that the bundle has been reloaded.
152
+     */
153
+    private static class CustomDevCommandsHandler implements ReactInstanceDevCommandsHandler {
154
+        private ReactInstanceDevCommandsHandler mCommandsHandler;
155
+
156
+        public CustomDevCommandsHandler(ReactInstanceDevCommandsHandler commandsHandler) {
157
+            mCommandsHandler = commandsHandler;
158
+        }
159
+
160
+        @Override
161
+        public void onReloadWithJSDebugger(JavaJSExecutor.Factory proxyExecutorFactory) {
162
+            onJSBundleReloaded();
163
+            mCommandsHandler.onReloadWithJSDebugger(proxyExecutorFactory);
164
+        }
165
+
166
+        @Override
167
+        public void onJSBundleLoadedFromServer() {
168
+            onJSBundleReloaded();
169
+            mCommandsHandler.onJSBundleLoadedFromServer();
170
+        }
171
+
172
+        /**
173
+         * Detach previously added ReactRootViews before handling bundle.
174
+         */
175
+        private void onJSBundleReloaded() {
176
+            BaseReactActivity context = ContextProvider.getActivityContext();
177
+            if (context != null) {
178
+                context.onJSBundleReloaded();
179
+            }
180
+        }
181
+
182
+        @Override
183
+        public void toggleElementInspector() {
184
+            mCommandsHandler.toggleElementInspector();
185
+        }
186
+    }
116
 }
187
 }
117
 
188
 

+ 0
- 2
android/app/src/main/java/com/reactnativenavigation/utils/IconUtils.java View File

1
 package com.reactnativenavigation.utils;
1
 package com.reactnativenavigation.utils;
2
 
2
 
3
 import android.content.Context;
3
 import android.content.Context;
4
-import android.content.res.Resources;
5
 import android.graphics.Bitmap;
4
 import android.graphics.Bitmap;
6
 import android.graphics.BitmapFactory;
5
 import android.graphics.BitmapFactory;
7
 import android.graphics.drawable.BitmapDrawable;
6
 import android.graphics.drawable.BitmapDrawable;
8
 import android.graphics.drawable.Drawable;
7
 import android.graphics.drawable.Drawable;
9
 import android.net.Uri;
8
 import android.net.Uri;
10
-import android.util.DisplayMetrics;
11
 
9
 
12
 import com.reactnativenavigation.BuildConfig;
10
 import com.reactnativenavigation.BuildConfig;
13
 
11
 

+ 25
- 4
android/app/src/main/java/com/reactnativenavigation/utils/ReflectionUtils.java View File

1
 package com.reactnativenavigation.utils;
1
 package com.reactnativenavigation.utils;
2
 
2
 
3
 import java.lang.reflect.Field;
3
 import java.lang.reflect.Field;
4
+import java.lang.reflect.Method;
4
 
5
 
5
 /**
6
 /**
6
  * Created by guyc on 14/04/16.
7
  * Created by guyc on 14/04/16.
7
  */
8
  */
8
 public class ReflectionUtils {
9
 public class ReflectionUtils {
9
 
10
 
10
-    public static boolean setBooleanField(Object obj, String name, Boolean value) {
11
+    public static boolean setField(Object obj, String name, Object value) {
11
         Field field;
12
         Field field;
12
         try {
13
         try {
13
             field = obj.getClass().getDeclaredField(name);
14
             field = obj.getClass().getDeclaredField(name);
14
             field.setAccessible(true);
15
             field.setAccessible(true);
15
             field.set(obj, value);
16
             field.set(obj, value);
16
             return true;
17
             return true;
17
-        } catch (NoSuchFieldException e) {
18
-            e.printStackTrace();
19
-        } catch (IllegalAccessException e) {
18
+        } catch (Exception e) {
20
             e.printStackTrace();
19
             e.printStackTrace();
21
         }
20
         }
22
         return false;
21
         return false;
23
     }
22
     }
23
+
24
+    public static Object getDeclaredField(Object obj, String fieldName) {
25
+        try {
26
+            Field f = obj.getClass().getDeclaredField(fieldName);
27
+            f.setAccessible(true);
28
+            return f.get(obj); //IllegalAccessException
29
+        } catch (Exception e) {
30
+            e.printStackTrace();
31
+        }
32
+        return null;
33
+    }
34
+
35
+    public static Object invoke(Object object, String methodName) {
36
+        try {
37
+            Method method = object.getClass().getDeclaredMethod(methodName);
38
+            method.setAccessible(true);
39
+            return method.invoke(object);
40
+        } catch (Exception e) {
41
+            e.printStackTrace();
42
+        }
43
+        return null;
44
+    }
24
 }
45
 }

+ 7
- 3
android/app/src/main/java/com/reactnativenavigation/views/RctView.java View File

67
      */
67
      */
68
     public void onTemporallyRemovedFromScreen() {
68
     public void onTemporallyRemovedFromScreen() {
69
         // Hack in order to prevent the react view from getting unmounted
69
         // Hack in order to prevent the react view from getting unmounted
70
-        ReflectionUtils.setBooleanField(mReactRootView, "mAttachScheduled", true);
70
+        ReflectionUtils.setField(mReactRootView, "mAttachScheduled", true);
71
     }
71
     }
72
 
72
 
73
     /**
73
     /**
75
      * executed and componentWillUnmount is called
75
      * executed and componentWillUnmount is called
76
      */
76
      */
77
     public void onRemoveFromScreen() {
77
     public void onRemoveFromScreen() {
78
-        ReflectionUtils.setBooleanField(mReactRootView, "mAttachScheduled", false);
78
+        ReflectionUtils.setField(mReactRootView, "mAttachScheduled", false);
79
     }
79
     }
80
 
80
 
81
     /**
81
     /**
83
      * executed and componentWillUnmount is called
83
      * executed and componentWillUnmount is called
84
      */
84
      */
85
     public void onReAddToScreen() {
85
     public void onReAddToScreen() {
86
-        ReflectionUtils.setBooleanField(mReactRootView, "mAttachScheduled", false);
86
+        ReflectionUtils.setField(mReactRootView, "mAttachScheduled", false);
87
+    }
88
+
89
+    public void detachFromScreen() {
90
+        ReflectionUtils.invoke(mReactRootView, "onDetachedFromWindow");
87
     }
91
     }
88
 }
92
 }
89
 
93
 

+ 11
- 0
android/app/src/main/java/com/reactnativenavigation/views/ScreenStack.java View File

165
 
165
 
166
         parent.addView(this, new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
166
         parent.addView(this, new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
167
     }
167
     }
168
+
169
+    public void removeAllReactViews() {
170
+        while (!mStack.empty()) {
171
+            RctView view = mStack.pop().view;
172
+            // Ensure view will be properly detached and unmounted
173
+            view.onRemoveFromScreen();
174
+            // Unmount the view
175
+            view.detachFromScreen();
176
+        }
177
+        removeAllViews();
178
+    }
168
 }
179
 }