Skip to content

Commit 8453cfc

Browse files
chore: revamp
1 parent c629092 commit 8453cfc

90 files changed

Lines changed: 2072 additions & 1940 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,5 @@ lib/
9090

9191
# Fastlane.swift runner binary
9292
**/fastlane/FastlaneRunner
93+
94+
.claude/

android/src/main/java/com/facebook/react/devsupport/DevInternalSettings.kt

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

android/src/main/java/com/facebook/react/devsupport/DevSupportManagerHelpers.kt

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

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import com.mendix.mendixnative.react.clearCachedReactNativeDevBundle
2121
import com.mendix.mendixnative.react.clearData
2222
import com.mendix.mendixnative.react.closeSqlDatabaseConnection
2323
import com.mendix.mendixnative.react.toggleElementInspector
24-
import com.mendixnative.MendixNativeModule
24+
import com.mendix.mendixnative.react.NativeReloadHandler
2525

2626
class MendixInitializer(
2727
private val context: Activity,
@@ -70,7 +70,9 @@ class MendixInitializer(
7070
devMenuTouchEventHandler =
7171
DevMenuTouchEventHandler(object : DevMenuTouchEventHandler.DevMenuTouchListener {
7272
override fun onTap() {
73-
reactNativeHost.typeSafeNativeModule<MendixNativeModule>()?.reloadClientWithState()
73+
reactNativeHost.reactApplicationContext()?.let {
74+
NativeReloadHandler(it).reload()
75+
}
7476
}
7577

7678
override fun onLongPress() {

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

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -88,30 +88,63 @@ fun clearDataWithReactContext(applicationContext: Application, reactNativeHost:
8888
}
8989

9090
fun deleteAppDatabaseAsync(reactContext: ReactContext?, cb: BooleanCallback) {
91-
reactContext?.typeSafeNativeModule<OPSQLiteModule>()?.let {
92-
it.deleteAllDBs()
93-
cb(true)
94-
} ?: cb(false)
91+
val opSQLiteModule = reactContext?.getNativeModule(OPSQLiteModule::class.java)
92+
if (opSQLiteModule != null) {
93+
try {
94+
opSQLiteModule.deleteAllDBs()
95+
cb(true)
96+
} catch (e: Exception) {
97+
Log.e("ClearData", "Failed to delete databases: ${e.message}")
98+
cb(false)
99+
}
100+
} else {
101+
cb(false)
102+
}
95103
}
96104

105+
/**
106+
* Clears all AsyncStorage data.
107+
*
108+
* Note: Previous implementation only checked module availability without clearing.
109+
* This now actually clears the storage using AsyncStorageModule.clear().
110+
*/
97111
fun clearAsyncStorage(reactNativeHost: ReactNativeHost): Boolean {
98-
val module = reactNativeHost.typeSafeNativeModule<AsyncStorageModule>()
99-
if (module != null) {
100-
return true
101-
} else {
102-
return false
112+
val asyncStorageModule = reactNativeHost.reactContext()?.getNativeModule(AsyncStorageModule::class.java)
113+
if (asyncStorageModule != null) {
114+
try {
115+
// Clear AsyncStorage synchronously - clear() expects a callback but we're using fire-and-forget
116+
asyncStorageModule.clear { error ->
117+
if (error != null) {
118+
Log.e("ClearData", "AsyncStorage clear error: $error")
119+
}
120+
}
121+
return true
122+
} catch (e: Exception) {
123+
Log.e("ClearData", "Failed to clear AsyncStorage: ${e.message}")
124+
return false
125+
}
103126
}
127+
return false
104128
}
105129

106130
fun clearSecureStorage(context: Context?): Boolean =
107131
context?.let { MendixEncryptedStorage.getMendixEncryptedStorage(it).clear() } ?: false
108132

109-
fun clearCookiesAsync(reactContext: ReactContext?, cb: (success: Boolean) -> Unit) =
110-
reactContext?.typeSafeNativeModule<NetworkingModule>()?.let { module ->
111-
module.clearCookies {
112-
cb(it[0] as Boolean)
133+
fun clearCookiesAsync(reactContext: ReactContext?, cb: (success: Boolean) -> Unit) {
134+
val networkingModule = reactContext?.getNativeModule(NetworkingModule::class.java)
135+
if (networkingModule != null) {
136+
try {
137+
networkingModule.clearCookies { result ->
138+
cb(result[0] as Boolean)
139+
}
140+
} catch (e: Exception) {
141+
Log.e("ClearData", "Failed to clear cookies: ${e.message}")
142+
cb(false)
113143
}
114-
} ?: cb(false)
144+
} else {
145+
cb(false)
146+
}
147+
}
115148

116149
fun clearCachedReactNativeDevBundle(applicationContext: Application) {
117150
try {
Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,21 @@
11
package com.mendix.mendixnative.react
22

3+
import android.util.Log
34
import com.facebook.react.bridge.ReactContext
45
import com.op.sqlite.OPSQLiteModule
56

7+
/**
8+
* Closes all SQLite database connections.
9+
*
10+
* This is called during app shutdown to gracefully close database connections.
11+
*/
612
fun closeSqlDatabaseConnection(reactContext: ReactContext?) {
7-
reactContext?.typeSafeNativeModule<OPSQLiteModule>()?.closeAllConnections()
13+
val opSQLiteModule = reactContext?.getNativeModule(OPSQLiteModule::class.java)
14+
if (opSQLiteModule != null) {
15+
try {
16+
opSQLiteModule.closeAllConnections()
17+
} catch (e: Exception) {
18+
Log.e("CloseApp", "Failed to close database connections: ${e.message}")
19+
}
20+
}
821
}

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

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,22 @@ package com.mendix.mendixnative.react
33
import com.facebook.common.logging.FLog
44
import com.facebook.react.bridge.ReactApplicationContext
55
import com.facebook.react.bridge.ReadableArray
6-
import com.facebook.react.bridge.WritableMap
7-
import com.facebook.react.bridge.WritableNativeMap
86
import com.facebook.react.modules.core.ExceptionsManagerModule
97

8+
/**
9+
* Handles JavaScript exceptions by reporting them to React Native's ExceptionsManager.
10+
*
11+
* This bridges JavaScript errors to the native exception handling system.
12+
*/
1013
class NativeErrorHandler(val reactContext: ReactApplicationContext) {
1114
fun handle(message: String?, stackTrace: ReadableArray?) {
12-
reactContext.typeSafeNativeModule<ExceptionsManagerModule>()?.reportSoftException(message, stackTrace, 0.0)
13-
// updateExceptionMessage is not available in RN 0.77.1
15+
// Use typed module access instead of generic typeSafeNativeModule
16+
val exceptionsManagerModule = reactContext.getNativeModule(ExceptionsManagerModule::class.java)
17+
exceptionsManagerModule?.reportSoftException(message, stackTrace, 0.0)
18+
19+
// Note: updateExceptionMessage is not available in RN 0.77.1+
1420
// ref: https://github.com/facebook/react-native/commit/4f47439a02183205ff6f68b1fc3bc392e78e4cb4
15-
// exceptionsManagerModule.updateExceptionMessage(message, stackTrace, 0);
21+
1622
FLog.e(javaClass, "Received JS exception: $message")
1723
}
1824
}

android/src/main/java/com/mendix/mendixnative/react/fs/NativeFsModule.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ class NativeFsModule(private val reactContext: ReactApplicationContext) {
3030

3131
fun save(blob: ReadableMap, filePath: String, promise: Promise) {
3232
val blobModule = reactContext.getNativeModule<BlobModule?>(BlobModule::class.java)
33-
val blobId: String = blob.getString("blobId") ?: run {""
33+
val blobId: String = blob.getString("blobId") ?: run {
3434
promise.reject(ERROR_INVALID_BLOB, "The specified blob is invalid")
3535
return
3636
}
Lines changed: 30 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
package com.mendix.mendixnative.util;
22

3-
import java.lang.reflect.Constructor;
43
import java.lang.reflect.Field;
5-
import java.lang.reflect.InvocationTargetException;
6-
import java.lang.reflect.Method;
74

5+
/**
6+
* Minimal reflection utilities for accessing React Native private fields.
7+
*
8+
* **Usage:** Only used by MendixShakeDetector to swap React Native's shake detector.
9+
* There is no public React Native API for this functionality.
10+
*
11+
* **Note:** Reflection should be avoided where possible. This class is kept minimal
12+
* and only exposes methods that are actively used.
13+
*/
814
public class ReflectionUtils {
915
private static Field findDeclaredField(Class<?> objectClass, String... fieldNames) {
1016
NoSuchFieldException lastException = null;
@@ -20,95 +26,27 @@ private static Field findDeclaredField(Class<?> objectClass, String... fieldName
2026
throw new RuntimeException(lastException);
2127
}
2228

23-
public static ConstructorWrapper findConstructor(String className, Class<?>... parameterTypes) {
24-
try {
25-
Constructor constructor = Class.forName(className).getDeclaredConstructor(parameterTypes);
26-
constructor.setAccessible(true);
27-
return new ConstructorWrapper(constructor);
28-
} catch (ClassNotFoundException | NoSuchMethodException e) {
29-
throw new RuntimeException(e);
30-
}
31-
}
32-
33-
// TODO: replace this with a lambda after upgrading to Java 8
34-
public static class ConstructorWrapper {
35-
private final Constructor constructor;
36-
37-
ConstructorWrapper(Constructor constructor) {
38-
this.constructor = constructor;
39-
}
40-
41-
public <T> T newInstance(Object... args) {
42-
try {
43-
return (T) constructor.newInstance(args);
44-
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
45-
throw new RuntimeException(e);
46-
}
47-
}
48-
}
49-
50-
public static MethodWrapper findMethod(Object object, String methodName, Class<?>... parameterTypes) {
51-
try {
52-
Method method = object.getClass().getDeclaredMethod(methodName, parameterTypes);
53-
method.setAccessible(true);
54-
return new MethodWrapper(method, object);
55-
} catch (NoSuchMethodException e) {
56-
throw new RuntimeException(e);
57-
}
58-
}
59-
60-
// TODO: replace this with a lambda after upgrading to Java 8
61-
public static class MethodWrapper {
62-
private final Method method;
63-
private final Object object;
64-
65-
MethodWrapper(Method method, Object object) {
66-
this.method = method;
67-
this.object = object;
68-
}
69-
70-
public void invoke(Object... args) {
71-
try {
72-
method.invoke(object, args);
73-
} catch (IllegalAccessException | InvocationTargetException e) {
74-
throw new RuntimeException(e);
75-
}
76-
}
77-
}
78-
79-
public static void setFieldOfSuperclass(Object object, String fieldName, Object value) {
80-
setFieldOfSuperclass(object, value, fieldName);
81-
}
82-
29+
/**
30+
* Sets a field on the superclass of the given object.
31+
* Tries multiple field names to handle React Native version differences.
32+
*
33+
* @param object The object whose superclass field should be set
34+
* @param value The value to set
35+
* @param fieldNames Field names to try (in order of preference)
36+
*/
8337
public static void setFieldOfSuperclass(Object object, Object value, String... fieldNames) {
8438
Field field = findDeclaredField(object.getClass().getSuperclass(), fieldNames);
8539
setField(object, field, value);
8640
}
8741

88-
public static void setField(Object object, String fieldName, Object value) {
89-
try {
90-
Field field = object.getClass().getDeclaredField(fieldName);
91-
setField(object, field, value);
92-
} catch (NoSuchFieldException e) {
93-
throw new RuntimeException(e);
94-
}
95-
}
96-
97-
private static void setField(Object object, Field field, Object value) {
98-
try {
99-
field.setAccessible(true);
100-
field.set(object, value);
101-
} catch (IllegalAccessException e) {
102-
throw new RuntimeException(e);
103-
} finally {
104-
field.setAccessible(false);
105-
}
106-
}
107-
108-
public static <T> T getFieldOfSuperclass(Object object, String fieldName) {
109-
return getFieldOfSuperclass(object, new String[] { fieldName });
110-
}
111-
42+
/**
43+
* Gets a field from the superclass of the given object.
44+
* Tries multiple field names to handle React Native version differences.
45+
*
46+
* @param object The object whose superclass field should be retrieved
47+
* @param fieldNames Field names to try (in order of preference)
48+
* @return The field value
49+
*/
11250
public static <T> T getFieldOfSuperclass(Object object, String... fieldNames) {
11351
try {
11452
Field field = findDeclaredField(object.getClass().getSuperclass(), fieldNames);
@@ -119,14 +57,14 @@ public static <T> T getFieldOfSuperclass(Object object, String... fieldNames) {
11957
}
12058
}
12159

122-
public static <T> T getField(Object object, String fieldName) {
60+
private static void setField(Object object, Field field, Object value) {
12361
try {
124-
Field field = object.getClass().getDeclaredField(fieldName);
12562
field.setAccessible(true);
126-
return (T) field.get(object);
127-
} catch (NoSuchFieldException | IllegalAccessException | ClassCastException e) {
63+
field.set(object, value);
64+
} catch (IllegalAccessException e) {
12865
throw new RuntimeException(e);
66+
} finally {
67+
field.setAccessible(false);
12968
}
13069
}
131-
13270
}

0 commit comments

Comments
 (0)