Skip to content

Commit 4ce6dea

Browse files
refactor: update deprecated ReactNativeHost usage to ReactHost across the application
refactor: replace ExceptionsManagerModule with DevSupportManager for error handling
1 parent b686643 commit 4ce6dea

7 files changed

Lines changed: 58 additions & 94 deletions

File tree

android/src/main/java/com/mendix/mendixnative/MendixReactApplication.kt

Lines changed: 5 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package com.mendix.mendixnative
22

33
import android.app.Application
44
import com.facebook.react.ReactHost
5-
import com.facebook.react.ReactNativeHost
65
import com.facebook.react.ReactPackage
76
import com.facebook.react.bridge.JSBundleLoader
87
import com.facebook.react.bridge.JSBundleLoaderDelegate
@@ -28,8 +27,6 @@ import com.mendixnative.MendixNativePackage
2827
import java.util.*
2928
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load
3029

31-
import com.facebook.react.defaults.DefaultReactNativeHost
32-
3330
abstract class MendixReactApplication : Application(), MendixApplication, ErrorHandlerFactory {
3431
private val appSessionId = "" + Math.random() * 1000 + Date().time
3532
override fun getAppSessionId(): String = appSessionId
@@ -44,33 +41,11 @@ abstract class MendixReactApplication : Application(), MendixApplication, ErrorH
4441

4542
private var jsBundleFileProvider: JSBundleFileProvider? = jsBundleProvider
4643

47-
override var reactNativeHost: ReactNativeHost = object : DefaultReactNativeHost(this) {
48-
override fun getUseDeveloperSupport(): Boolean = this@MendixReactApplication.useDeveloperSupport
49-
50-
override fun getPackages(): List<ReactPackage> {
51-
val pkgs: MutableList<ReactPackage> = ArrayList()
52-
// Use the packages provided by the concrete Application subclass.
53-
pkgs.addAll(this@MendixReactApplication.packages)
54-
// Inject splashScreenPresenter into any MendixNativePackage instances without creating duplicates.
55-
applyInternalPackageAugmentations(pkgs)
56-
return pkgs
57-
}
58-
59-
override fun getJSBundleFile(): String? = this@MendixReactApplication.jsBundleFile
60-
override fun getJSMainModuleName(): String = "index"
61-
override fun getBundleAssetName(): String? = super.getBundleAssetName()
62-
override fun getRedBoxHandler(): RedBoxHandler? = null
63-
64-
// Hermes & New Arch flags; Hermes executor will be picked automatically when isHermesEnabled is true.
65-
override val isNewArchEnabled: Boolean = true
66-
override val isHermesEnabled: Boolean = true
67-
}
68-
6944
/**
70-
* Build the [ReactHost] ourselves instead of using [DefaultReactHost.getDefaultReactHost],
71-
* because that factory evaluates [ReactNativeHost.getJSBundleFile] once at creation time and
72-
* bakes the result into a fixed [JSBundleLoader]. After an OTA update deploys a new bundle,
73-
* a subsequent [ReactHost.reload] would still load the stale bundle.
45+
* Build the [ReactHost] with a custom [JSBundleLoader] instead of using a static bundle path.
46+
* The default approach evaluates the bundle file path once at creation time and bakes it into
47+
* a fixed [JSBundleLoader]. After an OTA update deploys a new bundle, a subsequent
48+
* [ReactHost.reload] would still load the stale bundle.
7449
*
7550
* By providing a **dynamic** [JSBundleLoader] whose [JSBundleLoader.loadScript] calls
7651
* [getJSBundleFile] on every invocation, each reload picks up the latest bundle path —
@@ -133,10 +108,7 @@ abstract class MendixReactApplication : Application(), MendixApplication, ErrorH
133108
override fun onCreate() {
134109
super.onCreate()
135110
SoLoader.init(this, OpenSourceMergedSoMapping)
136-
// Only load the New Architecture entry point when enabled (always true here, but guarded for safety).
137-
if (reactNativeHost is DefaultReactNativeHost) {
138-
load()
139-
}
111+
load()
140112
}
141113

142114
override fun getJSBundleFile(): String? {

android/src/main/java/com/mendix/mendixnative/activity/MendixReactActivity.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ open class MendixReactActivity : ReactActivity(), DevAppMenuHandler, LaunchScree
5050
}
5151

5252
private val currentReactContext: ReactContext?
53-
get() = if (reactNativeHost.hasInstance()) reactInstanceManager.currentReactContext else null
53+
get() = reactHost.currentReactContext
5454

5555
val currentDevSupportManager: DevSupportManager?
5656
get() = reactHost.devSupportManager

android/src/main/java/com/mendix/mendixnative/fragment/MendixReactFragment.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,8 @@ open class MendixReactFragment : ReactFragment(), MendixReactFragmentView {
7474
}
7575

7676
fun onNewIntent(intent: Intent) {
77-
if (reactNativeHost.hasInstance()) {
78-
reactNativeHost.reactInstanceManager.onNewIntent(intent);
77+
reactHost?.currentReactContext?.let {
78+
it.onNewIntent(it.currentActivity, intent)
7979
}
8080
}
8181

android/src/main/java/com/mendix/mendixnative/fragment/ReactFragment.kt

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import androidx.fragment.app.Fragment
1010
import com.facebook.react.ReactApplication
1111
import com.facebook.react.ReactDelegate
1212
import com.facebook.react.ReactHost
13-
import com.facebook.react.ReactNativeHost
1413
import com.facebook.react.modules.core.PermissionAwareActivity
1514
import com.facebook.react.modules.core.PermissionListener
1615
import com.mendix.mendixnative.react.CopiedFrom
@@ -35,17 +34,15 @@ open class ReactFragment : Fragment(), PermissionAwareActivity {
3534
launchOptions = requireArguments().getBundle(ARG_LAUNCH_OPTIONS)
3635
}
3736
checkNotNull(mainComponentName) { "Cannot loadApp if component name is null" }
38-
mReactDelegate = activity?.let { ReactDelegate(it, reactHost, mainComponentName, launchOptions) }
37+
mReactDelegate = activity?.let { ReactDelegate(it, reactHost!!, mainComponentName, launchOptions) }
3938
}
4039

4140
/**
42-
* Get the [ReactNativeHost] used by this app. By default, assumes [ ][Activity.getApplication] is an instance of [ReactApplication] and calls [ ][ReactApplication.getReactNativeHost]. Override this method if your application class does not
43-
* implement `ReactApplication` or you simply have a different mechanism for storing a
44-
* `ReactNativeHost`, e.g. as a static field somewhere.
41+
* Get the [ReactHost] used by this app. By default, assumes [ ][Activity.getApplication] is an
42+
* instance of [ReactApplication] and calls [ ][ReactApplication.getReactHost]. Override this
43+
* method if your application class does not implement `ReactApplication` or you simply have a
44+
* different mechanism for storing a `ReactHost`, e.g. as a static field somewhere.
4545
*/
46-
protected val reactNativeHost: ReactNativeHost
47-
get() = (requireActivity().application as ReactApplication).reactNativeHost
48-
4946
protected val reactHost: ReactHost?
5047
get() = (requireActivity().application as ReactApplication).reactHost
5148

android/src/main/java/com/mendix/mendixnative/react/MxConfiguration.kt

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,9 @@ class MxConfiguration(val reactContext: ReactApplicationContext) {
1212
val application = (reactContext.applicationContext as MendixApplication)
1313
if (runtimeUrl == null) {
1414
if (warningsFilter != WarningsFilter.none) {
15-
application.reactNativeHost
16-
.reactInstanceManager
17-
.devSupportManager
18-
.showNewJavaError(
15+
application.reactHost
16+
?.devSupportManager
17+
?.showNewJavaError(
1918
"Runtime URL not specified.",
2019
Throwable("Without the runtime URL, the app cannot retrieve any data.\n\nPlease redeploy the app.")
2120
)
Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,47 @@
11
package com.mendix.mendixnative.react
22

33
import com.facebook.common.logging.FLog
4+
import com.facebook.react.ReactApplication
5+
import com.facebook.react.bridge.Arguments
46
import com.facebook.react.bridge.ReactApplicationContext
57
import com.facebook.react.bridge.ReadableArray
6-
import com.facebook.react.modules.core.ExceptionsManagerModule
8+
import com.facebook.react.bridge.ReadableMap
9+
import com.facebook.react.devsupport.StackTraceHelper
710

811
class NativeErrorHandler(val reactContext: ReactApplicationContext) {
912
fun handle(message: String?, stackTrace: ReadableArray?) {
10-
reactContext.nativeModule<ExceptionsManagerModule>(ExceptionsManagerModule.NAME)?.reportSoftException(message, stackTrace, 0.0)
1113
FLog.e(javaClass, "Received JS exception: $message")
14+
// In bridgeless mode, use DevSupportManager directly for proper error display
15+
val reactHost = (reactContext.applicationContext as? ReactApplication)?.reactHost
16+
reactHost?.devSupportManager?.showNewJSError(message, sanitize(stackTrace), -1)
17+
}
18+
19+
/**
20+
* Filter out invalid stack frames to prevent parsing errors.
21+
*
22+
* React Native's StackTraceHelper.convertJsStackTrace() uses requireNotNull() for
23+
* methodName and file. Invalid frames cause secondary errors that break RedBox and reload.
24+
*
25+
* Simply skip frames that don't have the required non-null fields.
26+
*/
27+
private fun sanitize(stackTrace: ReadableArray?): ReadableArray {
28+
val filtered = Arguments.createArray()
29+
if (stackTrace == null) return filtered
30+
(0 until stackTrace.size())
31+
.mapNotNull { stackTrace.getMap(it) }
32+
.filter { isValidFrame(it) }
33+
.forEach { filtered.pushMap(it) }
34+
35+
return filtered
36+
}
37+
38+
/**
39+
* Check if a stack frame has the required non-null fields for StackTraceHelper.
40+
* Uses React Native's own key constants to match their validation logic.
41+
*/
42+
private fun isValidFrame(frame: ReadableMap): Boolean {
43+
return arrayOf(StackTraceHelper.FILE_KEY, StackTraceHelper.METHOD_NAME_KEY).all {
44+
frame.hasKey(it) && !frame.isNull(it)
45+
}
1246
}
1347
}
Lines changed: 6 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,26 @@
11
package mendixnative.example
22

3-
import android.app.Application
43
import com.facebook.react.PackageList
5-
import com.facebook.react.ReactApplication
6-
import com.facebook.react.ReactHost
7-
import com.facebook.react.ReactNativeHost
84
import com.facebook.react.ReactPackage
9-
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load
10-
import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
11-
import com.facebook.react.defaults.DefaultReactNativeHost
12-
import com.facebook.react.soloader.OpenSourceMergedSoMapping
13-
import com.facebook.soloader.SoLoader
14-
15-
//Start - For MendixApplication compatibility only, not part of React Native template
16-
import com.mendix.mendixnative.MendixApplication
5+
import com.mendix.mendixnative.MendixReactApplication
176
import com.mendix.mendixnative.react.splash.MendixSplashScreenPresenter
187
import com.mendix.mendixnative.react.MxConfiguration
198

209
class SplashScreenPresenter: MendixSplashScreenPresenter {
2110
override fun show(activity: android.app.Activity) {}
2211
override fun hide(activity: android.app.Activity) {}
2312
}
24-
//End - For MendixApplication compatibility only, not part of React Native template
25-
26-
class MainApplication : Application(), MendixApplication {
27-
28-
override val reactNativeHost: ReactNativeHost =
29-
object : DefaultReactNativeHost(this) {
30-
override fun getPackages(): List<ReactPackage> =
31-
PackageList(this).packages.apply {
32-
// Packages that cannot be autolinked yet can be added manually here, for example:
33-
// add(MyReactNativePackage())
34-
}
35-
36-
override fun getJSMainModuleName(): String = "index"
37-
38-
override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG
39-
40-
override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
41-
override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED
42-
}
4313

44-
override val reactHost: ReactHost
45-
get() = getDefaultReactHost(applicationContext, reactNativeHost)
14+
class MainApplication : MendixReactApplication() {
4615

4716
override fun onCreate() {
4817
super.onCreate()
49-
MxConfiguration.runtimeUrl = "http://10.0.2.2:8081" //For MendixApplication compatibility only, not part of React Native template
50-
SoLoader.init(this, OpenSourceMergedSoMapping)
51-
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
52-
// If you opted-in for the New Architecture, we load the native entry point for this app.
53-
load()
54-
}
18+
MxConfiguration.runtimeUrl = "http://10.0.2.2:8081"
5519
}
5620

57-
//Start - For MendixApplication compatibility only, not part of React Native template
58-
override fun getUseDeveloperSupport() = false
21+
override fun getUseDeveloperSupport() = BuildConfig.DEBUG
5922
override fun createSplashScreenPresenter() = SplashScreenPresenter()
6023
override fun getPackages(): List<ReactPackage> = PackageList(this).packages
61-
override fun getJSBundleFile() = null
62-
override fun getAppSessionId() = null
63-
//End - For MendixApplication compatibility only, not part of React Native template
24+
override fun getJSBundleFile(): String? = null
25+
override fun getAppSessionId() = ""
6426
}

0 commit comments

Comments
 (0)