Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
1b748ce
chore: update RN to v084
vadymv-mendix May 8, 2026
cb2c5b2
chore: downgraded react to satisfy react-native v0.84
MxKevinBeqo May 13, 2026
b0a8bac
chore: bump Native Binary Version
MxKevinBeqo May 13, 2026
a07a3e6
chore: revamp
YogendraShelke Apr 2, 2026
ce52a85
test: add new tests
YogendraShelke Apr 16, 2026
083cfeb
chore: update RN to v084
vadymv-mendix May 8, 2026
5b6ca03
refactor: streamline type definitions and remove unused code
YogendraShelke May 12, 2026
2b582de
chore: update hermes-engine and React-Core-prebuilt checksums in Podf…
YogendraShelke May 12, 2026
8eab392
feat: add RN_HARNESS configuration for app registry and view flattening
YogendraShelke May 12, 2026
4886110
chore: replace deprecated apis with modern
YogendraShelke May 13, 2026
18a417f
feat: integrate ReactAppDependencyProvider and refactor ReactNativeDe…
YogendraShelke May 13, 2026
c1b1b8a
fix: handle file deletion on download failure and improve error handling
YogendraShelke May 13, 2026
1ec9b64
feat: enhance development experience with improved debug settings and…
YogendraShelke May 13, 2026
3a7b84b
fix: downgrade react version from 19.2.4 to 19.2.3 in package.json an…
YogendraShelke May 13, 2026
568be58
fix: update emulator configurations for Android and iOS in rn-harness…
YogendraShelke May 13, 2026
20d4ece
fix: downgrade React version to 19.2.3 and update React Native Harnes…
YogendraShelke May 13, 2026
73951b4
refactor: clean up code by removing unnecessary comments and improvin…
YogendraShelke May 14, 2026
15919a8
refactor: update AppDelegate to use bundleURL method and clean up Rea…
YogendraShelke May 14, 2026
621c85f
refactor: remove outdated DevSettings tests
YogendraShelke May 14, 2026
ff3d757
refactor: revert unnecessary changes
YogendraShelke May 14, 2026
3697e3d
refactor: fix indentation in getDevInternalSettings function
YogendraShelke May 14, 2026
e2f48cc
refactor: remove outdated code
YogendraShelke May 14, 2026
7335246
refactor: update DevHelper and ReactHostHelper for improved module ac…
YogendraShelke May 14, 2026
9a8114c
refactor: remove unused imports and clean up touch event handling in …
YogendraShelke May 14, 2026
e4a0d8d
refactor: improve conditional checks for showing the development menu
YogendraShelke May 14, 2026
f548a96
refactor: rename MxSplashScreen to MendixSplashScreen for consistency
YogendraShelke May 15, 2026
2ac4402
Merge pull request #34 from mendix/mx-native-revamp
YogendraShelke May 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/CI_DOCUMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ All workflows use standardized tooling versions to ensure consistency:
| ---------------- | --------- | ----------------------------- |
| **Node.js** | `24` | `.nvmrc` |
| **Yarn** | `4.12.0` | `package.json#packageManager` |
| **React Native** | `0.83.4` | `package.json` |
| **React** | `19.2.14` | `package.json` |
| **React Native** | `0.84.1` | `package.json` |
| **React** | `19.2.3` | `package.json` |
| **TypeScript** | `5.9.3` | `package.json` |

### Android Tooling
Expand Down Expand Up @@ -55,7 +55,7 @@ All workflows use standardized tooling versions to ensure consistency:

| Tool | Version | Defined In |
| ------------------------ | --------------------- | ----------------------- |
| **React Native Harness** | `1.0.0-alpha.21` | `package.json` |
| **React Native Harness** | `1.1.0` | `package.json` |
| **Android Emulator** | Pixel_API_35 (API 35) | `rn-harness.config.mjs` |
| **iOS Simulator** | iPhone 17 (iOS 26.2) | `rn-harness.config.mjs` |

Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,6 @@ lib/

# Fastlane.swift runner binary
**/fastlane/FastlaneRunner

# Harness logs/crash reports
example/.harness
5 changes: 3 additions & 2 deletions MendixNative.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ Pod::Spec.new do |s|
s.source = { :git => "https://github.com/mendix/mendix-native.git", :tag => "#{s.version}" }

s.source_files = "ios/**/*.{h,m,mm,cpp,swift}"
s.public_header_files = "ios/**/*.h"
s.private_header_files = "ios/**/*.h"
s.public_header_files = "ios/Modules/Helper/ReactHostHelper.h"
s.private_header_files = "ios/TurboModules/**/*.h"

s.dependency "SSZipArchive"
s.dependency "RNCAsyncStorage"
s.dependency "ReactAppDependencyProvider"

install_modules_dependencies(s)
end
4 changes: 1 addition & 3 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,6 @@ android {
}

buildFeatures {
dataBinding true
viewBinding true
buildConfig true
}

Expand Down Expand Up @@ -88,7 +86,7 @@ dependencies {
kapt 'com.github.bumptech.glide:compiler:4.12.0'


api "com.facebook.react:react-android:0.83.4"
api "com.facebook.react:react-android:0.84.1"
api project(':op-engineering_op-sqlite')
api project(':react-native-async-storage_async-storage')
api project(':react-native-gesture-handler')
Expand Down

This file was deleted.

This file was deleted.

92 changes: 10 additions & 82 deletions android/src/main/java/com/mendix/mendixnative/MendixInitializer.kt
Original file line number Diff line number Diff line change
@@ -1,44 +1,25 @@
package com.mendix.mendixnative

import android.app.Activity
import android.view.MotionEvent
import com.facebook.react.ReactHost
import com.facebook.react.ReactInstanceEventListener
import com.facebook.react.ReactNativeHost
import com.facebook.react.bridge.ReactContext
import com.facebook.react.common.ShakeDetector
import com.facebook.react.devsupport.DevSupportManagerBase
import com.facebook.react.devsupport.attachMendixSupportManagerShakeDetector
import com.facebook.react.devsupport.makeShakeDetector
import com.facebook.react.modules.network.OkHttpClientProvider
import com.mendix.mendixnative.config.AppPreferences
import com.mendix.mendixnative.handler.DevMenuTouchEventHandler
import com.mendix.mendixnative.react.*
import com.mendix.mendixnative.request.MendixNetworkInterceptor
import com.mendix.mendixnative.util.CookieEncryption
import com.mendix.mendixnative.react.MxConfiguration
import com.mendix.mendixnative.react.clearCachedReactNativeDevBundle
import com.mendix.mendixnative.react.clearData
import com.mendix.mendixnative.react.closeSqlDatabaseConnection
import com.mendix.mendixnative.react.toggleElementInspector
import com.mendixnative.MendixNativeModule

class MendixInitializer(
private val context: Activity,
private val reactHost: ReactHost,
private val reactNativeHost: ReactNativeHost,
private val hasRNDeveloperSupport: Boolean = false,
) : ReactInstanceEventListener {
private var shakeDetector: ShakeDetector? = null
private var devMenuTouchEventHandler: DevMenuTouchEventHandler? = null
) {

fun onCreate(
mendixApp: MendixApp,
devAppMenuHandler: DevAppMenuHandler = object : DevAppMenuHandler {
override fun showDevAppMenu() {}
},
clearData: Boolean,
) {
fun onCreate(mendixApp: MendixApp, clearData: Boolean) {
// Assign mendix xas id interceptor to okhttp
CookieEncryption.init(this.context)
if (CookieEncryption.isCookieEncryptionEnabled()) {
Expand All @@ -53,77 +34,25 @@ class MendixInitializer(
MxConfiguration.runtimeUrl = runtimeUrl
MxConfiguration.warningsFilter = mendixApp.warningsFilter

// This is here to make sure that a clean host instance is initialised.
restartReactInstanceManager()
// Reload only if there's already a running instance.
if (reactHost.currentReactContext != null) {
reactHost.reload("Clean start for new Mendix app")
}
if (clearData) clearData(context.application)
if (hasRNDeveloperSupport) setupDeveloperApp(runtimeUrl, mendixApp)
if (mendixApp.attachCustomDeveloperMenu) attachCustomDeveloperMenu(devAppMenuHandler)
}

private fun restartReactInstanceManager() {
if (reactNativeHost.hasInstance()) reactNativeHost.clear()
// Pre-initialize reactInstanceManager to be available for other methods
if (reactNativeHost.hasInstance()) reactNativeHost.reactInstanceManager
}

private fun attachCustomDeveloperMenu(devAppMenuHandler: DevAppMenuHandler) {
devMenuTouchEventHandler =
DevMenuTouchEventHandler(object : DevMenuTouchEventHandler.DevMenuTouchListener {
override fun onTap() {
reactNativeHost.typeSafeNativeModule<MendixNativeModule>()?.reloadClientWithState()
}

override fun onLongPress() {
devAppMenuHandler.showDevAppMenu()
}
})

attachShakeDetector(devAppMenuHandler)
}

fun onDestroy() {
// Stop shaking as early as possible to avoid orphaned dialogs
stopShakeDetector()

if (hasRNDeveloperSupport) {
AppPreferences(context.applicationContext).setElementInspector(false)
reactHost.removeReactInstanceEventListener(this)
}

// We need to clear the host to allow for reinitialization of the Native Modules
// Especially for when switching between apps
reactNativeHost.clear()
// Destroy the current instance but keep the host reusable — invalidate() is terminal
// in bridgeless mode and would prevent the host from ever starting a new instance.
reactHost.destroy("MendixInitializer.onDestroy()", null)

// We need to close all databases separately to avoid hitting a read only state exception
// Databases need to close after we are done closing the react native host to avoid db locks
closeSqlDatabaseConnection(reactNativeHost.reactApplicationContext())
}

fun stopShakeDetector() {
shakeDetector?.stop()
}

override fun onReactContextInitialized(context: ReactContext) {
val preferences = AppPreferences(context)
if (preferences.isElementInspectorEnabled) {
toggleElementInspector(context)
}
}

fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
return devMenuTouchEventHandler?.handle(ev) ?: false
}

private fun attachShakeDetector(devAppMenuHandler: DevAppMenuHandler) {
if (shakeDetector == null) {
shakeDetector = makeShakeDetector(context.applicationContext) {
devAppMenuHandler.showDevAppMenu()
}
}

(reactHost.devSupportManager as? DevSupportManagerBase)?.run {
attachMendixSupportManagerShakeDetector(shakeDetector!!, this)
}
closeSqlDatabaseConnection(reactHost.currentReactContext)
}

private fun setupDeveloperApp(
Expand All @@ -137,7 +66,6 @@ class MendixInitializer(
preferences.setDevMode((mendixApp.showExtendedDevMenu))

clearCachedReactNativeDevBundle(context.application)
reactHost.addReactInstanceEventListener(this)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import com.facebook.react.common.annotations.UnstableReactNativeAPI
import com.facebook.react.defaults.DefaultComponentsRegistry
import com.facebook.react.defaults.DefaultReactHostDelegate
import com.facebook.react.defaults.DefaultTurboModuleManagerDelegate
import com.facebook.react.devsupport.interfaces.DevBundleDownloadListener
import com.facebook.react.devsupport.interfaces.RedBoxHandler
import com.facebook.react.fabric.ComponentFactory
import com.facebook.react.runtime.ReactHostImpl
Expand All @@ -20,6 +21,7 @@ import com.mendix.mendixnative.error.ErrorHandler
import com.mendix.mendixnative.error.ErrorHandlerFactory
import com.mendix.mendixnative.error.mapErrorHandlerToRedBox
import com.mendix.mendixnative.handler.DummyErrorHandler
import com.mendix.mendixnative.devsupport.MendixDevSupportManagerFactory
import com.mendix.mendixnative.react.ota.OtaJSBundleUrlProvider
import com.mendix.mendixnative.react.splash.MendixSplashScreenPresenter
import com.mendixnative.MendixNativePackage
Expand Down Expand Up @@ -106,11 +108,12 @@ abstract class MendixReactApplication : Application(), MendixApplication, ErrorH
val componentFactory = ComponentFactory()
DefaultComponentsRegistry.register(componentFactory)
ReactHostImpl(
applicationContext,
delegate,
componentFactory,
true /* allowPackagerServerAccess */,
useDeveloperSupport,
context = applicationContext,
reactHostDelegate = delegate,
componentFactory = componentFactory,
allowPackagerServerAccess = true,
useDevSupport = useDeveloperSupport,
devSupportManagerFactory = MendixDevSupportManagerFactory(devBundleDownloadListener),
)
}

Expand Down Expand Up @@ -158,4 +161,14 @@ abstract class MendixReactApplication : Application(), MendixApplication, ErrorH

open val jsBundleProvider: JSBundleFileProvider?
get() = null

/**
* Override this to provide a [DevBundleDownloadListener] that receives bundle download
* progress, success, and failure callbacks. This is used to drive custom loading UIs.
*
* The listener is injected at [ReactHost] creation time via [MendixDevSupportManagerFactory].
* Use a delegating holder pattern if the actual listener is created after app initialization.
*/
open val devBundleDownloadListener: DevBundleDownloadListener?
get() = null
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package com.mendix.mendixnative.activity

import android.os.Bundle
import android.view.KeyEvent
import android.view.MotionEvent
import com.facebook.react.ReactActivity
import com.facebook.react.ReactActivityDelegate
import com.facebook.react.bridge.ReactContext
Expand Down Expand Up @@ -31,8 +30,8 @@ open class MendixReactActivity : ReactActivity(), DevAppMenuHandler, LaunchScree
?: throw ClassCastException("Application needs to implement MendixApplication")

mendixInitializer =
MendixInitializer(this, reactHost, reactNativeHost, mendixApplication.useDeveloperSupport)
mendixInitializer.onCreate(mendixApp!!, this, intent.getBooleanExtra(CLEAR_DATA, false))
MendixInitializer(this, reactHost, mendixApplication.useDeveloperSupport)
mendixInitializer.onCreate(mendixApp!!, intent.getBooleanExtra(CLEAR_DATA, false))

super.onCreate(null)
}
Expand All @@ -42,12 +41,6 @@ open class MendixReactActivity : ReactActivity(), DevAppMenuHandler, LaunchScree
super.onDestroy()
}

override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
return if (mendixInitializer.dispatchTouchEvent(ev)) {
true
} else super.dispatchTouchEvent(ev)
}

override fun getMainComponentName(): String? {
return MAIN_COMPONENT_NAME
}
Expand All @@ -66,7 +59,9 @@ open class MendixReactActivity : ReactActivity(), DevAppMenuHandler, LaunchScree
return object : ReactActivityDelegate(this, mainComponentName) {
override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
if (keyCode == KeyEvent.KEYCODE_MENU) {
showDevAppMenu()
if (mendixApp?.showExtendedDevMenu == true) {
showDevAppMenu()
}
return true
}
return super.onKeyUp(keyCode, event)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.mendix.mendixnative.devsupport

import android.content.Context
import com.facebook.react.bridge.UiThreadUtil
import com.facebook.react.common.SurfaceDelegateFactory
import com.facebook.react.devsupport.DevSupportManagerBase
import com.facebook.react.devsupport.ReactInstanceDevHelper
import com.facebook.react.devsupport.interfaces.DevBundleDownloadListener
import com.facebook.react.devsupport.interfaces.DevLoadingViewManager
import com.facebook.react.devsupport.interfaces.PausedInDebuggerOverlayManager
import com.facebook.react.devsupport.interfaces.RedBoxHandler
import com.facebook.react.packagerconnection.RequestHandler

/**
* A public DevSupportManager implementation for Mendix apps in bridgeless mode.
*
* This mirrors the behavior of React Native's internal [BridgelessDevSupportManager]
* but is accessible from external modules, allowing us to inject a custom
* [DevBundleDownloadListener] at construction time without reflection.
*/
class MendixBridgelessDevSupportManager(
applicationContext: Context,
reactInstanceManagerHelper: ReactInstanceDevHelper,
packagerPathForJSBundleName: String?,
enableOnCreate: Boolean,
redBoxHandler: RedBoxHandler?,
devBundleDownloadListener: DevBundleDownloadListener?,
minNumShakes: Int,
customPackagerCommandHandlers: Map<String, RequestHandler>?,
surfaceDelegateFactory: SurfaceDelegateFactory?,
devLoadingViewManager: DevLoadingViewManager?,
pausedInDebuggerOverlayManager: PausedInDebuggerOverlayManager?,
) : DevSupportManagerBase(
applicationContext,
reactInstanceManagerHelper,
packagerPathForJSBundleName,
enableOnCreate,
redBoxHandler,
devBundleDownloadListener,
minNumShakes,
customPackagerCommandHandlers,
surfaceDelegateFactory,
devLoadingViewManager,
pausedInDebuggerOverlayManager,
) {
override val uniqueTag: String get() = "MendixBridgeless"

override fun handleReloadJS() {
UiThreadUtil.assertOnUiThread()
hideRedboxDialog()
reactInstanceDevHelper.reload("MendixBridgelessDevSupportManager.handleReloadJS()")
}
}
Loading
Loading