Skip to content

Commit 584f8a4

Browse files
authored
Merge pull request #51 from Soomgo-Mobile/fabric-bridgeless
feat(Native): Support New Architecture (Bridge/Bridgeless)
2 parents 2d9e32d + e310541 commit 584f8a4

File tree

5 files changed

+103
-87
lines changed

5 files changed

+103
-87
lines changed

android/app/src/main/java/com/microsoft/codepush/react/CodePush.java

Lines changed: 8 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -50,15 +50,18 @@ public class CodePush implements ReactPackage {
5050
private static ReactInstanceHolder mReactInstanceHolder;
5151
private static CodePush mCurrentInstance;
5252

53-
public CodePush(Context context) {
54-
this(context, false);
55-
}
56-
5753
public static String getServiceUrl() {
5854
return mServerUrl;
5955
}
6056

61-
public CodePush(Context context, boolean isDebugMode) {
57+
public static synchronized CodePush getInstance(Context context, boolean isDebugMode) {
58+
if (mCurrentInstance == null) {
59+
mCurrentInstance = new CodePush(context, isDebugMode);
60+
}
61+
return mCurrentInstance;
62+
}
63+
64+
private CodePush(Context context, boolean isDebugMode) {
6265
mContext = context.getApplicationContext();
6366

6467
mUpdateManager = new CodePushUpdateManager(context.getFilesDir().getAbsolutePath());
@@ -88,27 +91,6 @@ public CodePush(Context context, boolean isDebugMode) {
8891
initializeUpdateAfterRestart();
8992
}
9093

91-
public CodePush(Context context, boolean isDebugMode, String serverUrl) {
92-
this(context, isDebugMode);
93-
mServerUrl = serverUrl;
94-
}
95-
96-
public CodePush(Context context, boolean isDebugMode, int publicKeyResourceDescriptor) {
97-
this(context, isDebugMode);
98-
99-
mPublicKey = getPublicKeyByResourceDescriptor(publicKeyResourceDescriptor);
100-
}
101-
102-
public CodePush(Context context, boolean isDebugMode, String serverUrl, Integer publicKeyResourceDescriptor) {
103-
this(context, isDebugMode);
104-
105-
if (publicKeyResourceDescriptor != null) {
106-
mPublicKey = getPublicKeyByResourceDescriptor(publicKeyResourceDescriptor);
107-
}
108-
109-
mServerUrl = serverUrl;
110-
}
111-
11294
private String getPublicKeyByResourceDescriptor(int publicKeyResourceDescriptor){
11395
String publicKey;
11496
try {

android/app/src/main/java/com/microsoft/codepush/react/CodePushBuilder.java

Lines changed: 0 additions & 37 deletions
This file was deleted.

android/app/src/main/java/com/microsoft/codepush/react/CodePushNativeModule.java

Lines changed: 92 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,15 @@
55
import android.os.AsyncTask;
66
import android.os.Handler;
77
import android.os.Looper;
8-
import android.provider.Settings;
98
import android.view.View;
109

10+
import androidx.annotation.OptIn;
11+
1112
import com.facebook.react.ReactApplication;
13+
import com.facebook.react.ReactDelegate;
14+
import com.facebook.react.ReactHost;
1215
import com.facebook.react.ReactInstanceManager;
16+
import com.facebook.react.ReactActivity;
1317
import com.facebook.react.ReactRootView;
1418
import com.facebook.react.bridge.Arguments;
1519
import com.facebook.react.bridge.JSBundleLoader;
@@ -20,20 +24,22 @@
2024
import com.facebook.react.bridge.ReactMethod;
2125
import com.facebook.react.bridge.ReadableMap;
2226
import com.facebook.react.bridge.WritableMap;
27+
import com.facebook.react.common.annotations.UnstableReactNativeAPI;
2328
import com.facebook.react.modules.core.ChoreographerCompat;
2429
import com.facebook.react.modules.core.DeviceEventManagerModule;
2530
import com.facebook.react.modules.core.ReactChoreographer;
31+
import com.facebook.react.runtime.ReactHostDelegate;
2632

2733
import org.json.JSONArray;
2834
import org.json.JSONException;
2935
import org.json.JSONObject;
3036

3137
import java.io.IOException;
3238
import java.lang.reflect.Field;
39+
import java.lang.reflect.Method;
3340
import java.util.ArrayList;
3441
import java.util.Date;
3542
import java.util.HashMap;
36-
import java.util.List;
3743
import java.util.Map;
3844
import java.util.UUID;
3945

@@ -120,15 +126,38 @@ private void setJSBundle(ReactInstanceManager instanceManager, String latestJSBu
120126
latestJSBundleLoader = JSBundleLoader.createFileLoader(latestJSBundleFile);
121127
}
122128

123-
Field bundleLoaderField = instanceManager.getClass().getDeclaredField("mBundleLoader");
124-
bundleLoaderField.setAccessible(true);
125-
bundleLoaderField.set(instanceManager, latestJSBundleLoader);
129+
ReactHost reactHost = resolveReactHost();
130+
if (reactHost == null) {
131+
// Bridge, Old Architecture and RN < 0.74 (we support Bridgeless >= 0.74)
132+
setJSBundleLoaderBridge(instanceManager, latestJSBundleLoader);
133+
return;
134+
}
135+
136+
// Bridgeless (RN >= 0.74)
137+
setJSBundleLoaderBridgeless(reactHost, latestJSBundleLoader);
126138
} catch (Exception e) {
127139
CodePushUtils.log("Unable to set JSBundle - CodePush may not support this version of React Native");
128140
throw new IllegalAccessException("Could not setJSBundle");
129141
}
130142
}
131143

144+
private void setJSBundleLoaderBridge(ReactInstanceManager instanceManager, JSBundleLoader latestJSBundleLoader) throws NoSuchFieldException, IllegalAccessException {
145+
Field bundleLoaderField = instanceManager.getClass().getDeclaredField("mBundleLoader");
146+
bundleLoaderField.setAccessible(true);
147+
bundleLoaderField.set(instanceManager, latestJSBundleLoader);
148+
}
149+
150+
@OptIn(markerClass = UnstableReactNativeAPI.class)
151+
private void setJSBundleLoaderBridgeless(ReactHost reactHost, JSBundleLoader latestJSBundleLoader) throws NoSuchFieldException, IllegalAccessException {
152+
Field mReactHostDelegateField = reactHost.getClass().getDeclaredField("mReactHostDelegate");
153+
mReactHostDelegateField.setAccessible(true);
154+
ReactHostDelegate reactHostDelegate = (ReactHostDelegate) mReactHostDelegateField.get(reactHost);
155+
assert reactHostDelegate != null;
156+
Field jsBundleLoaderField = reactHostDelegate.getClass().getDeclaredField("jsBundleLoader");
157+
jsBundleLoaderField.setAccessible(true);
158+
jsBundleLoaderField.set(reactHostDelegate, latestJSBundleLoader);
159+
}
160+
132161
private void loadBundle() {
133162
clearLifecycleEventListener();
134163
try {
@@ -156,12 +185,22 @@ private void loadBundle() {
156185
@Override
157186
public void run() {
158187
try {
159-
// We don't need to resetReactRootViews anymore
160-
// due the issue https://github.com/facebook/react-native/issues/14533
161-
// has been fixed in RN 0.46.0
162-
//resetReactRootViews(instanceManager);
188+
// reload method introduced in RN 0.74 (https://github.com/reactwg/react-native-new-architecture/discussions/174)
189+
// so, we need to check if reload method exists and call it
190+
try {
191+
ReactDelegate reactDelegate = resolveReactDelegate();
192+
if (reactDelegate == null) {
193+
throw new NoSuchMethodException("ReactDelegate doesn't have reload method in RN < 0.74");
194+
}
195+
196+
resetReactRootViews(reactDelegate);
163197

164-
instanceManager.recreateReactContextInBackground();
198+
Method reloadMethod = reactDelegate.getClass().getMethod("reload");
199+
reloadMethod.invoke(reactDelegate);
200+
} catch (NoSuchMethodException e) {
201+
// RN < 0.74 calls ReactInstanceManager.recreateReactContextInBackground() directly
202+
instanceManager.recreateReactContextInBackground();
203+
}
165204
mCodePush.initializeUpdateAfterRestart();
166205
} catch (Exception e) {
167206
// The recreation method threw an unknown exception
@@ -179,18 +218,19 @@ public void run() {
179218
}
180219
}
181220

182-
// This workaround has been implemented in order to fix https://github.com/facebook/react-native/issues/14533
183-
// resetReactRootViews allows to call recreateReactContextInBackground without any exceptions
184-
// This fix also relates to https://github.com/microsoft/react-native-code-push/issues/878
185-
private void resetReactRootViews(ReactInstanceManager instanceManager) throws NoSuchFieldException, IllegalAccessException {
186-
Field mAttachedRootViewsField = instanceManager.getClass().getDeclaredField("mAttachedRootViews");
187-
mAttachedRootViewsField.setAccessible(true);
188-
List<ReactRootView> mAttachedRootViews = (List<ReactRootView>)mAttachedRootViewsField.get(instanceManager);
189-
for (ReactRootView reactRootView : mAttachedRootViews) {
190-
reactRootView.removeAllViews();
191-
reactRootView.setId(View.NO_ID);
221+
// Fix freezing that occurs when reloading the app (RN >= 0.77.1 Old Architecture)
222+
// - "Trying to add a root view with an explicit id (11) already set.
223+
// React Native uses the id field to track react tags and will overwrite this field.
224+
// If that is fine, explicitly overwrite the id field to View.NO_ID before calling addRootView."
225+
private void resetReactRootViews(ReactDelegate reactDelegate) {
226+
ReactActivity currentActivity = (ReactActivity) getCurrentActivity();
227+
if (currentActivity != null) {
228+
ReactRootView reactRootView = reactDelegate.getReactRootView();
229+
if (reactRootView != null) {
230+
reactRootView.removeAllViews();
231+
reactRootView.setId(View.NO_ID);
232+
}
192233
}
193-
mAttachedRootViewsField.set(instanceManager, mAttachedRootViews);
194234
}
195235

196236
private void clearLifecycleEventListener() {
@@ -201,6 +241,36 @@ private void clearLifecycleEventListener() {
201241
}
202242
}
203243

244+
private ReactDelegate resolveReactDelegate() {
245+
ReactActivity currentActivity = (ReactActivity) getCurrentActivity();
246+
if (currentActivity == null) {
247+
return null;
248+
}
249+
250+
try {
251+
Method getReactDelegateMethod = currentActivity.getClass().getMethod("getReactDelegate");
252+
return (ReactDelegate) getReactDelegateMethod.invoke(currentActivity);
253+
} catch (Exception e) {
254+
// RN < 0.74 doesn't have getReactDelegate method
255+
return null;
256+
}
257+
}
258+
259+
private ReactHost resolveReactHost() {
260+
ReactDelegate reactDelegate = resolveReactDelegate();
261+
if (reactDelegate == null) {
262+
return null;
263+
}
264+
265+
try {
266+
Field reactHostField = reactDelegate.getClass().getDeclaredField("mReactHost");
267+
reactHostField.setAccessible(true);
268+
return (ReactHost) reactHostField.get(reactDelegate);
269+
} catch (Exception e) {
270+
return null;
271+
}
272+
}
273+
204274
// Use reflection to find the ReactInstanceManager. See #556 for a proposal for a less brittle way to approach this.
205275
private ReactInstanceManager resolveInstanceManager() throws NoSuchFieldException, IllegalAccessException {
206276
ReactInstanceManager instanceManager = CodePush.getReactInstanceManager();
@@ -492,7 +562,7 @@ protected Void doInBackground(Void... params) {
492562
return null;
493563
}
494564
}
495-
565+
496566
promise.resolve("");
497567
} catch(CodePushUnknownException e) {
498568
CodePushUtils.log(e);

ios/CodePush/CodePush.m

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#import <React/RCTEventDispatcher.h>
66
#import <React/RCTRootView.h>
77
#import <React/RCTUtils.h>
8+
#import <React/RCTReloadCommand.h>
89
#else // back compatibility for RN version < 0.40
910
#import "RCTAssert.h"
1011
#import "RCTBridgeModule.h"
@@ -540,7 +541,7 @@ - (void)loadBundle
540541
[super.bridge setValue:[CodePush bundleURL] forKey:@"bundleURL"];
541542
}
542543

543-
[super.bridge reload];
544+
RCTTriggerReloadCommandListeners(@"CodePush reload");
544545
});
545546
}
546547

react-native.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ module.exports = {
44
android: {
55
packageImportPath: "import com.microsoft.codepush.react.CodePush;",
66
packageInstance:
7-
"new CodePush(getApplicationContext(), BuildConfig.DEBUG)",
7+
"CodePush.getInstance(getApplicationContext(), BuildConfig.DEBUG)",
88
sourceDir: './android/app'
99
}
1010
}

0 commit comments

Comments
 (0)