@react-native-native/nativ-fabric 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/NativFabric.podspec +41 -0
  2. package/android/build.gradle +128 -0
  3. package/android/src/main/AndroidManifest.xml +2 -0
  4. package/android/src/main/cpp/CMakeLists.txt +59 -0
  5. package/android/src/main/cpp/NativBindingsInstaller.cpp +393 -0
  6. package/android/src/main/cpp/NativRuntime.cpp +508 -0
  7. package/android/src/main/java/com/nativfabric/ComposeHost.kt +26 -0
  8. package/android/src/main/java/com/nativfabric/NativContainerPackage.kt +35 -0
  9. package/android/src/main/java/com/nativfabric/NativContainerView.kt +51 -0
  10. package/android/src/main/java/com/nativfabric/NativContainerViewManager.kt +62 -0
  11. package/android/src/main/java/com/nativfabric/NativRuntime.kt +201 -0
  12. package/android/src/main/java/com/nativfabric/NativRuntimeModule.kt +37 -0
  13. package/android/src/main/java/com/nativfabric/compose/ComposeWrappers.kt +45 -0
  14. package/app.plugin.js +159 -0
  15. package/expo-module.config.json +6 -0
  16. package/ios/NativContainerComponentView.mm +137 -0
  17. package/ios/NativRuntime.h +36 -0
  18. package/ios/NativRuntime.mm +549 -0
  19. package/metro/Nativ.h +126 -0
  20. package/metro/compilers/android-compiler.js +339 -0
  21. package/metro/compilers/dylib-compiler.js +474 -0
  22. package/metro/compilers/kotlin-compiler.js +632 -0
  23. package/metro/compilers/rust-compiler.js +722 -0
  24. package/metro/compilers/static-compiler.js +1118 -0
  25. package/metro/compilers/swift-compiler.js +363 -0
  26. package/metro/extractors/cpp-ast-extractor.js +126 -0
  27. package/metro/extractors/kotlin-extractor.js +125 -0
  28. package/metro/extractors/rust-extractor.js +118 -0
  29. package/metro/index.js +236 -0
  30. package/metro/transformer.js +649 -0
  31. package/metro/utils/bridge-generator.js +50 -0
  32. package/metro/utils/compile-commands.js +104 -0
  33. package/metro/utils/cpp-daemon.js +344 -0
  34. package/metro/utils/dts-generator.js +32 -0
  35. package/metro/utils/include-resolver.js +73 -0
  36. package/metro/utils/kotlin-daemon.js +394 -0
  37. package/metro/utils/type-mapper.js +63 -0
  38. package/package.json +43 -0
  39. package/react-native.config.js +13 -0
  40. package/src/NativContainerNativeComponent.ts +9 -0
  41. package/src/NativeNativRuntime.ts +8 -0
  42. package/src/index.ts +4 -0
@@ -0,0 +1,508 @@
1
+ // NativRuntime JNI bridge — Android equivalent of iOS NativRuntime.
2
+ // Maintains the render registry + sync function registry.
3
+ // User .so dylibs register via __attribute__((constructor)) at dlopen time.
4
+
5
+ #include <jni.h>
6
+ #include <string>
7
+ #include <unordered_map>
8
+ #include <unordered_set>
9
+ #include <android/log.h>
10
+
11
+ #define LOG_TAG "NativRuntime"
12
+ #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
13
+ #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
14
+
15
+ // ─── Registry types (same C ABI as iOS) ────────────────────────────────
16
+
17
+ typedef const char* (*NativSyncFn)(const char* argsJson);
18
+ typedef void (*NativAsyncFn)(const char* argsJson, void (*resolve)(const char*), void (*reject)(const char*, const char*));
19
+ typedef void (*NativRenderFn)(void* view_handle, float width, float height,
20
+ void* runtime, void* props);
21
+
22
+ // ─── Registries ────────────────────────────────────────────────────────
23
+
24
+ static std::unordered_map<std::string, NativSyncFn>& getSyncRegistry() {
25
+ static std::unordered_map<std::string, NativSyncFn> reg;
26
+ return reg;
27
+ }
28
+
29
+ static std::unordered_map<std::string, NativAsyncFn>& getAsyncRegistry() {
30
+ static std::unordered_map<std::string, NativAsyncFn> reg;
31
+ return reg;
32
+ }
33
+
34
+ static std::unordered_map<std::string, NativRenderFn>& getRenderRegistry() {
35
+ static std::unordered_map<std::string, NativRenderFn> reg;
36
+ return reg;
37
+ }
38
+
39
+ // ─── Props snapshot (passed to render functions) ───────────────────────
40
+
41
+ struct PropsSnapshot {
42
+ std::unordered_map<std::string, std::string> strings;
43
+ std::unordered_map<std::string, double> numbers;
44
+ std::unordered_map<std::string, bool> bools;
45
+ std::unordered_set<std::string> callbacks;
46
+ };
47
+
48
+ // Current render's props (set during tryRender, read by nativ_jsi_get_*)
49
+ static thread_local PropsSnapshot* g_currentProps = nullptr;
50
+ static thread_local std::string g_currentComponent;
51
+
52
+ // Current render's JNI state (for view manipulation from user dylibs)
53
+ static thread_local JNIEnv* g_currentEnv = nullptr;
54
+ static thread_local jobject g_currentView = nullptr;
55
+
56
+ // ─── C ABI exports (called by user .so dylibs via constructor) ─────────
57
+
58
+ extern "C" {
59
+
60
+ void nativ_register_sync(const char* moduleId, const char* fnName, NativSyncFn fn) {
61
+ auto key = std::string(moduleId) + "::" + fnName;
62
+ getSyncRegistry()[key] = fn;
63
+ LOGI("registered sync: %s", key.c_str());
64
+ }
65
+
66
+ void nativ_register_async(const char* moduleId, const char* fnName, NativAsyncFn fn) {
67
+ auto key = std::string(moduleId) + "::" + fnName;
68
+ getAsyncRegistry()[key] = fn;
69
+ LOGI("registered async: %s", key.c_str());
70
+ }
71
+
72
+ void nativ_register_render(const char* componentId, NativRenderFn fn) {
73
+ getRenderRegistry()[std::string(componentId)] = fn;
74
+ LOGI("registered render: %s", componentId);
75
+ }
76
+
77
+ // ─── Props access (called by Rust/C++ render functions) ────────────────
78
+
79
+ const char* nativ_jsi_get_string(void* runtime, void* object, const char* prop_name) {
80
+ if (!g_currentProps) return "";
81
+ auto it = g_currentProps->strings.find(std::string(prop_name));
82
+ if (it != g_currentProps->strings.end()) {
83
+ static thread_local std::string buf;
84
+ buf = it->second;
85
+ return buf.c_str();
86
+ }
87
+ return "";
88
+ }
89
+
90
+ double nativ_jsi_get_number(void* runtime, void* object, const char* prop_name) {
91
+ if (!g_currentProps) return 0.0;
92
+ auto it = g_currentProps->numbers.find(std::string(prop_name));
93
+ if (it != g_currentProps->numbers.end()) return it->second;
94
+ return 0.0;
95
+ }
96
+
97
+ int nativ_jsi_get_bool(void* runtime, void* object, const char* prop_name) {
98
+ if (!g_currentProps) return 0;
99
+ auto it = g_currentProps->bools.find(std::string(prop_name));
100
+ if (it != g_currentProps->bools.end()) return it->second ? 1 : 0;
101
+ return 0;
102
+ }
103
+
104
+ int nativ_jsi_has_prop(void* runtime, void* object, const char* prop_name) {
105
+ if (!g_currentProps) return 0;
106
+ std::string name(prop_name);
107
+ return (g_currentProps->strings.count(name) ||
108
+ g_currentProps->numbers.count(name) ||
109
+ g_currentProps->bools.count(name)) ? 1 : 0;
110
+ }
111
+
112
+ void nativ_jsi_call_function(void* runtime, void* object, const char* prop_name) {
113
+ // TODO: callbacks via JNI → JS bridge
114
+ LOGI("call_function('%s') — not yet implemented on Android", prop_name);
115
+ }
116
+
117
+ void nativ_jsi_call_function_with_string(void* runtime, void* object,
118
+ const char* prop_name, const char* arg) {
119
+ LOGI("call_function_with_string('%s') — not yet implemented on Android", prop_name);
120
+ }
121
+
122
+ // Keep view register stubs for ABI compat with iOS dylibs
123
+ void nativ_register_view(const char*, void*) {}
124
+ void nativ_unregister_view(const char*) {}
125
+
126
+ // Direct C dispatch — called from NativBindingsInstaller.cpp
127
+ const char* nativ_call_sync(const char* moduleId, const char* fnName, const char* argsJson) {
128
+ auto key = std::string(moduleId) + "::" + fnName;
129
+ auto &reg = getSyncRegistry();
130
+ auto it = reg.find(key);
131
+ if (it == reg.end()) return nullptr;
132
+ return it->second(argsJson);
133
+ }
134
+
135
+ // Async dispatch — returns the function pointer (BindingsInstaller handles Promise + threading)
136
+ NativAsyncFn nativ_get_async_fn(const char* moduleId, const char* fnName) {
137
+ auto key = std::string(moduleId) + "::" + fnName;
138
+ auto &reg = getAsyncRegistry();
139
+ auto it = reg.find(key);
140
+ if (it == reg.end()) return nullptr;
141
+ return it->second;
142
+ }
143
+
144
+ // ─── Android view manipulation (called by Rust/C++ render functions) ──
145
+
146
+ void nativ_view_set_background_color(void* view, double r, double g, double b, double a) {
147
+ if (!g_currentEnv) return;
148
+ JNIEnv* env = g_currentEnv;
149
+ jobject targetView = view ? (jobject)view : g_currentView;
150
+ if (!targetView) return;
151
+
152
+ int color = ((int)(a * 255) << 24) | ((int)(r * 255) << 16) |
153
+ ((int)(g * 255) << 8) | (int)(b * 255);
154
+
155
+ jclass viewClass = env->FindClass("android/view/View");
156
+ jmethodID setBg = env->GetMethodID(viewClass, "setBackgroundColor", "(I)V");
157
+ env->CallVoidMethod(targetView, setBg, color);
158
+ env->DeleteLocalRef(viewClass);
159
+ }
160
+
161
+ void nativ_view_add_label(void* parent, const char* text,
162
+ double r, double g, double b,
163
+ double width, double height) {
164
+ if (!g_currentEnv) return;
165
+ JNIEnv* env = g_currentEnv;
166
+ jobject parentView = parent ? (jobject)parent : g_currentView;
167
+ if (!parentView) return;
168
+
169
+ // Get context from parent view
170
+ jclass viewClass = env->FindClass("android/view/View");
171
+ jmethodID getContext = env->GetMethodID(viewClass, "getContext",
172
+ "()Landroid/content/Context;");
173
+ jobject context = env->CallObjectMethod(parentView, getContext);
174
+
175
+ // Create TextView
176
+ jclass tvClass = env->FindClass("android/widget/TextView");
177
+ jmethodID tvInit = env->GetMethodID(tvClass, "<init>",
178
+ "(Landroid/content/Context;)V");
179
+ jobject tv = env->NewObject(tvClass, tvInit, context);
180
+
181
+ // setText
182
+ jstring jText = env->NewStringUTF(text);
183
+ jmethodID setText = env->GetMethodID(tvClass, "setText",
184
+ "(Ljava/lang/CharSequence;)V");
185
+ env->CallVoidMethod(tv, setText, jText);
186
+
187
+ // setTextColor
188
+ int color = (0xFF << 24) | ((int)(r * 255) << 16) |
189
+ ((int)(g * 255) << 8) | (int)(b * 255);
190
+ jmethodID setColor = env->GetMethodID(tvClass, "setTextColor", "(I)V");
191
+ env->CallVoidMethod(tv, setColor, color);
192
+
193
+ // setGravity(Gravity.CENTER = 17)
194
+ jmethodID setGravity = env->GetMethodID(tvClass, "setGravity", "(I)V");
195
+ env->CallVoidMethod(tv, setGravity, 17);
196
+
197
+ // setTextSize
198
+ jmethodID setTextSize = env->GetMethodID(tvClass, "setTextSize", "(F)V");
199
+ env->CallVoidMethod(tv, setTextSize, 18.0f);
200
+
201
+ // LayoutParams = MATCH_PARENT, MATCH_PARENT
202
+ jclass lpClass = env->FindClass("android/widget/FrameLayout$LayoutParams");
203
+ jmethodID lpInit = env->GetMethodID(lpClass, "<init>", "(II)V");
204
+ jobject lp = env->NewObject(lpClass, lpInit, -1, -1);
205
+
206
+ // addView(tv, lp)
207
+ jclass vgClass = env->FindClass("android/view/ViewGroup");
208
+ jmethodID addView = env->GetMethodID(vgClass, "addView",
209
+ "(Landroid/view/View;Landroid/view/ViewGroup$LayoutParams;)V");
210
+ env->CallVoidMethod(parentView, addView, tv, lp);
211
+
212
+ if (env->ExceptionCheck()) {
213
+ LOGE("add_label: exception after addView");
214
+ env->ExceptionDescribe();
215
+ env->ExceptionClear();
216
+ } else {
217
+ LOGI("add_label: added '%s' to parent", text);
218
+ }
219
+
220
+ env->DeleteLocalRef(lp);
221
+ env->DeleteLocalRef(tv);
222
+ env->DeleteLocalRef(jText);
223
+ env->DeleteLocalRef(context);
224
+ env->DeleteLocalRef(lpClass);
225
+ env->DeleteLocalRef(vgClass);
226
+ env->DeleteLocalRef(tvClass);
227
+ env->DeleteLocalRef(viewClass);
228
+ }
229
+
230
+ void* nativ_view_add_subview(void* parent, double x, double y,
231
+ double w, double h,
232
+ double r, double g, double b, double a) {
233
+ if (!g_currentEnv) return nullptr;
234
+ JNIEnv* env = g_currentEnv;
235
+ jobject parentView = parent ? (jobject)parent : g_currentView;
236
+ if (!parentView) return nullptr;
237
+
238
+ // Get context
239
+ jclass viewClass = env->FindClass("android/view/View");
240
+ jmethodID getContext = env->GetMethodID(viewClass, "getContext",
241
+ "()Landroid/content/Context;");
242
+ jobject context = env->CallObjectMethod(parentView, getContext);
243
+
244
+ // Create child View
245
+ jobject child = env->NewObject(viewClass,
246
+ env->GetMethodID(viewClass, "<init>", "(Landroid/content/Context;)V"),
247
+ context);
248
+
249
+ // setBackgroundColor
250
+ int color = ((int)(a * 255) << 24) | ((int)(r * 255) << 16) |
251
+ ((int)(g * 255) << 8) | (int)(b * 255);
252
+ jmethodID setBg = env->GetMethodID(viewClass, "setBackgroundColor", "(I)V");
253
+ env->CallVoidMethod(child, setBg, color);
254
+
255
+ // Convert dp coords to pixels
256
+ jclass dpClass = env->FindClass("android/util/TypedValue");
257
+ jmethodID applyDim = env->GetStaticMethodID(dpClass, "applyDimension",
258
+ "(IFLandroid/util/DisplayMetrics;)F");
259
+ jclass contextClass = env->FindClass("android/content/Context");
260
+ jmethodID getResources = env->GetMethodID(contextClass, "getResources",
261
+ "()Landroid/content/res/Resources;");
262
+ jobject resources = env->CallObjectMethod(context, getResources);
263
+ jclass resClass = env->GetObjectClass(resources);
264
+ jmethodID getMetrics = env->GetMethodID(resClass, "getDisplayMetrics",
265
+ "()Landroid/util/DisplayMetrics;");
266
+ jobject metrics = env->CallObjectMethod(resources, getMetrics);
267
+
268
+ // TypedValue.COMPLEX_UNIT_DIP = 1
269
+ float px = (float)x; // points ≈ dp on Android
270
+ float py = (float)y;
271
+ float pw = (float)w;
272
+ float ph = (float)h;
273
+ // Scale from points to pixels using density
274
+ jclass metricsClass = env->GetObjectClass(metrics);
275
+ jfieldID densityField = env->GetFieldID(metricsClass, "density", "F");
276
+ float density = env->GetFloatField(metrics, densityField);
277
+ int pxX = (int)(px * density);
278
+ int pxY = (int)(py * density);
279
+ int pxW = (int)(pw * density);
280
+ int pxH = (int)(ph * density);
281
+
282
+ // LayoutParams with position
283
+ jclass lpClass = env->FindClass("android/widget/FrameLayout$LayoutParams");
284
+ jmethodID lpInit = env->GetMethodID(lpClass, "<init>", "(II)V");
285
+ jobject lp = env->NewObject(lpClass, lpInit, pxW, pxH);
286
+
287
+ // Set margins for position (FrameLayout uses margins for positioning)
288
+ jfieldID leftMargin = env->GetFieldID(lpClass, "leftMargin", "I");
289
+ jfieldID topMargin = env->GetFieldID(lpClass, "topMargin", "I");
290
+ env->SetIntField(lp, leftMargin, pxX);
291
+ env->SetIntField(lp, topMargin, pxY);
292
+
293
+ // addView
294
+ jclass vgClass = env->FindClass("android/view/ViewGroup");
295
+ jmethodID addView = env->GetMethodID(vgClass, "addView",
296
+ "(Landroid/view/View;Landroid/view/ViewGroup$LayoutParams;)V");
297
+ env->CallVoidMethod(parentView, addView, child, lp);
298
+
299
+ // Return as global ref (user may reference it later in same render)
300
+ jobject globalChild = env->NewGlobalRef(child);
301
+
302
+ // Clean up local refs
303
+ env->DeleteLocalRef(lp);
304
+ env->DeleteLocalRef(child);
305
+ env->DeleteLocalRef(context);
306
+ env->DeleteLocalRef(resources);
307
+ env->DeleteLocalRef(metrics);
308
+ env->DeleteLocalRef(lpClass);
309
+ env->DeleteLocalRef(vgClass);
310
+ env->DeleteLocalRef(viewClass);
311
+ env->DeleteLocalRef(dpClass);
312
+ env->DeleteLocalRef(contextClass);
313
+ env->DeleteLocalRef(resClass);
314
+ env->DeleteLocalRef(metricsClass);
315
+
316
+ return globalChild;
317
+ }
318
+
319
+ } // extern "C"
320
+
321
+ // ─── JNI helpers ───────────────────────────────────────────────────────
322
+
323
+ static std::string jstringToString(JNIEnv* env, jstring jstr) {
324
+ if (!jstr) return "";
325
+ const char* chars = env->GetStringUTFChars(jstr, nullptr);
326
+ std::string result(chars);
327
+ env->ReleaseStringUTFChars(jstr, chars);
328
+ return result;
329
+ }
330
+
331
+ // Convert a Java Map<String, String> to C++ map
332
+ static std::unordered_map<std::string, std::string> jmapToStringMap(JNIEnv* env, jobject jmap) {
333
+ std::unordered_map<std::string, std::string> result;
334
+ if (!jmap) return result;
335
+
336
+ jclass mapClass = env->GetObjectClass(jmap);
337
+ jmethodID entrySet = env->GetMethodID(mapClass, "entrySet", "()Ljava/util/Set;");
338
+ jobject set = env->CallObjectMethod(jmap, entrySet);
339
+
340
+ jclass setClass = env->GetObjectClass(set);
341
+ jmethodID iterator = env->GetMethodID(setClass, "iterator", "()Ljava/util/Iterator;");
342
+ jobject iter = env->CallObjectMethod(set, iterator);
343
+
344
+ jclass iterClass = env->GetObjectClass(iter);
345
+ jmethodID hasNext = env->GetMethodID(iterClass, "hasNext", "()Z");
346
+ jmethodID next = env->GetMethodID(iterClass, "next", "()Ljava/lang/Object;");
347
+
348
+ jclass entryClass = env->FindClass("java/util/Map$Entry");
349
+ jmethodID getKey = env->GetMethodID(entryClass, "getKey", "()Ljava/lang/Object;");
350
+ jmethodID getValue = env->GetMethodID(entryClass, "getValue", "()Ljava/lang/Object;");
351
+
352
+ while (env->CallBooleanMethod(iter, hasNext)) {
353
+ jobject entry = env->CallObjectMethod(iter, next);
354
+ auto key = jstringToString(env, (jstring)env->CallObjectMethod(entry, getKey));
355
+ auto val = jstringToString(env, (jstring)env->CallObjectMethod(entry, getValue));
356
+ result[key] = val;
357
+ env->DeleteLocalRef(entry);
358
+ }
359
+ return result;
360
+ }
361
+
362
+ // Convert a Java Map<String, Double> to C++ map
363
+ static std::unordered_map<std::string, double> jmapToDoubleMap(JNIEnv* env, jobject jmap) {
364
+ std::unordered_map<std::string, double> result;
365
+ if (!jmap) return result;
366
+
367
+ jclass mapClass = env->GetObjectClass(jmap);
368
+ jmethodID entrySet = env->GetMethodID(mapClass, "entrySet", "()Ljava/util/Set;");
369
+ jobject set = env->CallObjectMethod(jmap, entrySet);
370
+
371
+ jclass setClass = env->GetObjectClass(set);
372
+ jmethodID iterator = env->GetMethodID(setClass, "iterator", "()Ljava/util/Iterator;");
373
+ jobject iter = env->CallObjectMethod(set, iterator);
374
+
375
+ jclass iterClass = env->GetObjectClass(iter);
376
+ jmethodID hasNext = env->GetMethodID(iterClass, "hasNext", "()Z");
377
+ jmethodID next = env->GetMethodID(iterClass, "next", "()Ljava/lang/Object;");
378
+
379
+ jclass entryClass = env->FindClass("java/util/Map$Entry");
380
+ jmethodID getKey = env->GetMethodID(entryClass, "getKey", "()Ljava/lang/Object;");
381
+ jmethodID getValue = env->GetMethodID(entryClass, "getValue", "()Ljava/lang/Object;");
382
+
383
+ jclass doubleClass = env->FindClass("java/lang/Double");
384
+ jmethodID doubleValue = env->GetMethodID(doubleClass, "doubleValue", "()D");
385
+
386
+ while (env->CallBooleanMethod(iter, hasNext)) {
387
+ jobject entry = env->CallObjectMethod(iter, next);
388
+ auto key = jstringToString(env, (jstring)env->CallObjectMethod(entry, getKey));
389
+ jobject valObj = env->CallObjectMethod(entry, getValue);
390
+ double val = env->CallDoubleMethod(valObj, doubleValue);
391
+ result[key] = val;
392
+ env->DeleteLocalRef(valObj);
393
+ env->DeleteLocalRef(entry);
394
+ }
395
+ return result;
396
+ }
397
+
398
+ // Convert a Java Map<String, Boolean> to C++ map
399
+ static std::unordered_map<std::string, bool> jmapToBoolMap(JNIEnv* env, jobject jmap) {
400
+ std::unordered_map<std::string, bool> result;
401
+ if (!jmap) return result;
402
+
403
+ jclass mapClass = env->GetObjectClass(jmap);
404
+ jmethodID entrySet = env->GetMethodID(mapClass, "entrySet", "()Ljava/util/Set;");
405
+ jobject set = env->CallObjectMethod(jmap, entrySet);
406
+
407
+ jclass setClass = env->GetObjectClass(set);
408
+ jmethodID iterator = env->GetMethodID(setClass, "iterator", "()Ljava/util/Iterator;");
409
+ jobject iter = env->CallObjectMethod(set, iterator);
410
+
411
+ jclass iterClass = env->GetObjectClass(iter);
412
+ jmethodID hasNext = env->GetMethodID(iterClass, "hasNext", "()Z");
413
+ jmethodID next = env->GetMethodID(iterClass, "next", "()Ljava/lang/Object;");
414
+
415
+ jclass entryClass = env->FindClass("java/util/Map$Entry");
416
+ jmethodID getKey = env->GetMethodID(entryClass, "getKey", "()Ljava/lang/Object;");
417
+ jmethodID getValue = env->GetMethodID(entryClass, "getValue", "()Ljava/lang/Object;");
418
+
419
+ jclass boolClass = env->FindClass("java/lang/Boolean");
420
+ jmethodID boolValue = env->GetMethodID(boolClass, "booleanValue", "()Z");
421
+
422
+ while (env->CallBooleanMethod(iter, hasNext)) {
423
+ jobject entry = env->CallObjectMethod(iter, next);
424
+ auto key = jstringToString(env, (jstring)env->CallObjectMethod(entry, getKey));
425
+ jobject valObj = env->CallObjectMethod(entry, getValue);
426
+ bool val = env->CallBooleanMethod(valObj, boolValue);
427
+ result[key] = val;
428
+ env->DeleteLocalRef(valObj);
429
+ env->DeleteLocalRef(entry);
430
+ }
431
+ return result;
432
+ }
433
+
434
+ // ─── JNI exports ───────────────────────────────────────────────────────
435
+
436
+ extern "C" {
437
+
438
+ JNIEXPORT void JNICALL
439
+ Java_com_nativfabric_NativRuntime_nativeInit(JNIEnv* env, jobject thiz) {
440
+ LOGI("NativRuntime initialized");
441
+ }
442
+
443
+ JNIEXPORT void JNICALL
444
+ Java_com_nativfabric_NativRuntime_nativeTryRender(
445
+ JNIEnv* env, jobject thiz,
446
+ jstring jComponentId, jobject jView, jfloat width, jfloat height,
447
+ jobject jStrings, jobject jNumbers, jobject jBools
448
+ ) {
449
+ auto componentId = jstringToString(env, jComponentId);
450
+ LOGI("nativeTryRender: %s (%.0fx%.0f), registry size=%zu", componentId.c_str(), width, height, getRenderRegistry().size());
451
+ auto &reg = getRenderRegistry();
452
+ auto it = reg.find(componentId);
453
+ if (it == reg.end()) {
454
+ LOGE("nativeTryRender: render function not found for %s", componentId.c_str());
455
+ return;
456
+ }
457
+ LOGI("nativeTryRender: calling render function for %s", componentId.c_str());
458
+
459
+ // Build props snapshot from Java maps
460
+ PropsSnapshot props;
461
+ props.strings = jmapToStringMap(env, jStrings);
462
+ props.numbers = jmapToDoubleMap(env, jNumbers);
463
+ props.bools = jmapToBoolMap(env, jBools);
464
+
465
+ // Set thread-local state for the render call
466
+ g_currentProps = &props;
467
+ g_currentComponent = componentId;
468
+ g_currentEnv = env;
469
+ g_currentView = jView;
470
+
471
+ // Pass the view jobject as void* — render functions use nativ_view_* APIs
472
+ // which access g_currentEnv + g_currentView internally.
473
+ // Pass non-null sentinels for runtime/props — Android's nativ_jsi_get_*
474
+ // functions read from g_currentProps, but Props::new() requires both non-null.
475
+ static int runtimeSentinel = 1;
476
+ it->second(reinterpret_cast<void*>(jView), width, height,
477
+ reinterpret_cast<void*>(&runtimeSentinel),
478
+ reinterpret_cast<void*>(&props));
479
+
480
+ g_currentProps = nullptr;
481
+ g_currentComponent.clear();
482
+ g_currentEnv = nullptr;
483
+ g_currentView = nullptr;
484
+ }
485
+
486
+ JNIEXPORT jstring JNICALL
487
+ Java_com_nativfabric_NativRuntime_nativeCallSync(
488
+ JNIEnv* env, jobject thiz,
489
+ jstring jModuleId, jstring jFnName, jstring jArgsJson
490
+ ) {
491
+ auto moduleId = jstringToString(env, jModuleId);
492
+ auto fnName = jstringToString(env, jFnName);
493
+ auto argsJson = jstringToString(env, jArgsJson);
494
+
495
+ auto key = moduleId + "::" + fnName;
496
+ auto &reg = getSyncRegistry();
497
+ auto it = reg.find(key);
498
+ if (it == reg.end()) {
499
+ LOGE("Unknown function: %s", key.c_str());
500
+ return nullptr;
501
+ }
502
+
503
+ const char* result = it->second(argsJson.c_str());
504
+ if (!result) return nullptr;
505
+ return env->NewStringUTF(result);
506
+ }
507
+
508
+ } // extern "C"
@@ -0,0 +1,26 @@
1
+ package com.nativfabric
2
+
3
+ import android.view.ViewGroup
4
+ import android.widget.FrameLayout
5
+ import androidx.compose.runtime.Composable
6
+ import androidx.compose.ui.platform.ComposeView
7
+
8
+ /**
9
+ * ComposeHost — APK-side helper for .dex Compose components.
10
+ *
11
+ * The .dex can't call ComposeView.setContent directly (signature mismatch
12
+ * between compile-time and runtime due to Compose compiler transforms).
13
+ * This helper is compiled WITH the Gradle Compose plugin, so setContent works.
14
+ * The .dex calls ComposeHost.render(parent) { MyComposable(props) } instead.
15
+ */
16
+ object ComposeHost {
17
+ @JvmStatic
18
+ fun render(parent: ViewGroup, content: @Composable () -> Unit) {
19
+ val composeView = ComposeView(parent.context)
20
+ composeView.setContent(content)
21
+ parent.addView(composeView, FrameLayout.LayoutParams(
22
+ FrameLayout.LayoutParams.MATCH_PARENT,
23
+ FrameLayout.LayoutParams.MATCH_PARENT
24
+ ))
25
+ }
26
+ }
@@ -0,0 +1,35 @@
1
+ package com.nativfabric
2
+
3
+ import com.facebook.react.BaseReactPackage
4
+ import com.facebook.react.bridge.NativeModule
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.module.model.ReactModuleInfo
7
+ import com.facebook.react.module.model.ReactModuleInfoProvider
8
+ import com.facebook.react.uimanager.ViewManager
9
+
10
+ class NativContainerPackage : BaseReactPackage() {
11
+ override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
12
+ // Ensure NativRuntime has app context for DexClassLoader
13
+ if (NativRuntime.appContext == null) {
14
+ NativRuntime.appContext = reactContext.applicationContext
15
+ }
16
+ return listOf(NativContainerViewManager())
17
+ }
18
+
19
+ override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
20
+ return when (name) {
21
+ NativRuntimeModule.NAME -> NativRuntimeModule(reactContext)
22
+ else -> null
23
+ }
24
+ }
25
+
26
+ override fun getReactModuleInfoProvider() = ReactModuleInfoProvider {
27
+ mapOf(
28
+ NativRuntimeModule.NAME to ReactModuleInfo(
29
+ NativRuntimeModule.NAME,
30
+ NativRuntimeModule.NAME,
31
+ false, false, false, false, true
32
+ )
33
+ )
34
+ }
35
+ }
@@ -0,0 +1,51 @@
1
+ package com.nativfabric
2
+
3
+ import android.content.Context
4
+ import android.view.View
5
+ import android.widget.FrameLayout
6
+
7
+ /**
8
+ * NativContainerView — hosts native views rendered by Rust/C++/Kotlin/Compose.
9
+ *
10
+ * Simple FrameLayout. Render functions add children directly.
11
+ * We override onLayout to manually measure/layout children since
12
+ * Fabric/Yoga doesn't measure programmatically-added Android views.
13
+ */
14
+ class NativContainerView(context: Context) : FrameLayout(context) {
15
+ init {
16
+ setBackgroundColor(0x00000000)
17
+ }
18
+
19
+ override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
20
+ super.onLayout(changed, left, top, right, bottom)
21
+ // Manually measure+layout all children since Fabric doesn't know about them
22
+ val w = right - left
23
+ val h = bottom - top
24
+ if (w <= 0 || h <= 0) return
25
+ val wSpec = MeasureSpec.makeMeasureSpec(w, MeasureSpec.EXACTLY)
26
+ val hSpec = MeasureSpec.makeMeasureSpec(h, MeasureSpec.EXACTLY)
27
+ for (i in 0 until childCount) {
28
+ val child = getChildAt(i)
29
+ child.measure(wSpec, hSpec)
30
+ child.layout(0, 0, w, h)
31
+ }
32
+ }
33
+
34
+ override fun requestLayout() {
35
+ super.requestLayout()
36
+ // Fabric intercepts requestLayout — force a measure pass
37
+ post {
38
+ val w = width
39
+ val h = height
40
+ if (w > 0 && h > 0) {
41
+ val wSpec = MeasureSpec.makeMeasureSpec(w, MeasureSpec.EXACTLY)
42
+ val hSpec = MeasureSpec.makeMeasureSpec(h, MeasureSpec.EXACTLY)
43
+ for (i in 0 until childCount) {
44
+ val child = getChildAt(i)
45
+ child.measure(wSpec, hSpec)
46
+ child.layout(0, 0, w, h)
47
+ }
48
+ }
49
+ }
50
+ }
51
+ }
@@ -0,0 +1,62 @@
1
+ package com.nativfabric
2
+
3
+ import com.facebook.react.module.annotations.ReactModule
4
+ import com.facebook.react.uimanager.SimpleViewManager
5
+ import com.facebook.react.uimanager.ThemedReactContext
6
+ import com.facebook.react.uimanager.ViewManagerDelegate
7
+ import com.facebook.react.uimanager.annotations.ReactProp
8
+ import com.facebook.react.viewmanagers.NativContainerManagerInterface
9
+ import com.facebook.react.viewmanagers.NativContainerManagerDelegate
10
+
11
+ @ReactModule(name = NativContainerViewManager.NAME)
12
+ class NativContainerViewManager : SimpleViewManager<NativContainerView>(),
13
+ NativContainerManagerInterface<NativContainerView> {
14
+
15
+ private val delegate: ViewManagerDelegate<NativContainerView> =
16
+ NativContainerManagerDelegate(this)
17
+
18
+ override fun getDelegate(): ViewManagerDelegate<NativContainerView> = delegate
19
+
20
+ override fun getName(): String = NAME
21
+
22
+ override fun createViewInstance(context: ThemedReactContext): NativContainerView {
23
+ val view = NativContainerView(context)
24
+ view.addOnLayoutChangeListener { _, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom ->
25
+ val w = right - left
26
+ val h = bottom - top
27
+ val oldW = oldRight - oldLeft
28
+ val oldH = oldBottom - oldTop
29
+ if (w != oldW || h != oldH) {
30
+ renderComponent(view)
31
+ }
32
+ }
33
+ return view
34
+ }
35
+
36
+ @ReactProp(name = "componentId")
37
+ override fun setComponentId(view: NativContainerView?, value: String?) {
38
+ view?.tag = value
39
+ view?.let { renderComponent(it) }
40
+ }
41
+
42
+ @ReactProp(name = "propsJson")
43
+ override fun setPropsJson(view: NativContainerView?, value: String?) {
44
+ view?.let { renderComponent(it) }
45
+ }
46
+
47
+ private fun renderComponent(view: NativContainerView) {
48
+ val componentId = view.tag as? String ?: return
49
+ if (view.width <= 0 || view.height <= 0) {
50
+ view.post { renderComponent(view) }
51
+ return
52
+ }
53
+
54
+ // Render directly into the view
55
+ view.removeAllViews()
56
+ NativRuntime.tryRender(componentId, view, view.width.toFloat(), view.height.toFloat())
57
+ }
58
+
59
+ companion object {
60
+ const val NAME = "NativContainer"
61
+ }
62
+ }