diff --git a/packages/nitrogen/src/views/kotlin/KotlinHybridViewManager.ts b/packages/nitrogen/src/views/kotlin/KotlinHybridViewManager.ts index ae1f66bfa..6e5199e3d 100644 --- a/packages/nitrogen/src/views/kotlin/KotlinHybridViewManager.ts +++ b/packages/nitrogen/src/views/kotlin/KotlinHybridViewManager.ts @@ -4,15 +4,26 @@ import { createViewComponentShadowNodeFiles, getViewComponentNames, } from '../CppHybridViewComponent.js' -import { createFileMetadataString } from '../../syntax/helpers.js' +import { + createFileMetadataString, + escapeCppName, +} from '../../syntax/helpers.js' import { NitroConfig } from '../../config/NitroConfig.js' +import { getHybridObjectName } from '../../syntax/getHybridObjectName.js' +import { addJNINativeRegistration } from '../../syntax/kotlin/JNINativeRegistrations.js' +import { indent } from '../../utils.js' export function createKotlinHybridViewManager( spec: HybridObjectSpec ): SourceFile[] { const cppFiles = createViewComponentShadowNodeFiles(spec) - const javaNamespace = NitroConfig.getAndroidPackage('java/kotlin', 'views') - const { manager } = getViewComponentNames(spec) + const javaSubNamespace = NitroConfig.getAndroidPackage('java/kotlin', 'views') + const javaNamespace = NitroConfig.getAndroidPackage('java/kotlin') + const cxxNamespace = NitroConfig.getCxxNamespace('c++', 'views') + const { JHybridTSpec } = getHybridObjectName(spec.name) + const { manager, stateClassName, component, propsClassName } = + getViewComponentNames(spec) + const stateUpdaterName = `${stateClassName}Updater` const autolinking = NitroConfig.getAutolinkedHybridObjects() const viewImplementation = autolinking[spec.name]?.kotlin if (viewImplementation == null) { @@ -21,10 +32,10 @@ export function createKotlinHybridViewManager( ) } - const mmFile = ` + const viewManagerCode = ` ${createFileMetadataString(`${manager}.kt`)} -package ${javaNamespace} +package ${javaSubNamespace} import android.view.View import com.facebook.react.fabric.StateWrapperImpl @@ -32,7 +43,7 @@ import com.facebook.react.uimanager.ReactStylesDiffMap import com.facebook.react.uimanager.SimpleViewManager import com.facebook.react.uimanager.StateWrapper import com.facebook.react.uimanager.ThemedReactContext -import ${NitroConfig.getAndroidPackage('java/kotlin')}.* +import ${javaNamespace}.* /** * Represents the React Native \`ViewManager\` for the "${spec.name}" Nitro HybridView. @@ -59,21 +70,159 @@ class ${manager}: SimpleViewManager() { override fun updateState(view: View, props: ReactStylesDiffMap, stateWrapper: StateWrapper): Any? { val stateWrapperImpl = stateWrapper as? StateWrapperImpl ?: throw Error("StateWrapper uses a different implementation!") val hybridView = views[view] ?: throw Error("Couldn't find view $view in local views table!") - // TODO: Get props from stateWrapperImpl and update them in HybridView + + ${stateUpdaterName}.updateViewProps(hybridView, stateWrapperImpl) return super.updateState(view, props, stateWrapper) } } - ` + `.trim() + + const updaterKotlinCode = ` +${createFileMetadataString(`${stateUpdaterName}.kt`)} + +package ${javaSubNamespace} + +import com.facebook.react.fabric.StateWrapperImpl +import ${javaNamespace}.* + +class ${stateUpdaterName} { + companion object { + /** + * Updates the props for [view] through C++. + * The [state] prop is expected to contain [view]'s props as wrapped Fabric state. + */ + @Suppress("KotlinJniMissingFunction") + @JvmStatic + external fun updateViewProps(view: ${viewImplementation}, state: StateWrapperImpl) + } +} + `.trim() + + const updaterJniDescriptor = NitroConfig.getAndroidPackage( + 'java/kotlin', + 'views', + stateUpdaterName + ) + const updaterJniHeaderCode = ` +${createFileMetadataString(`J${stateUpdaterName}.hpp`)} + +#pragma once + +#if REACT_NATIVE_VERSION >= 78 + +#include +#include +#include "${JHybridTSpec}.hpp" + +namespace ${cxxNamespace} { + +using namespace facebook; + +class J${stateUpdaterName}: jni::HybridClass { +public: + static constexpr auto kJavaDescriptor = "L${updaterJniDescriptor};"; + +public: + static void updateViewProps(jni::alias_ref, + jni::alias_ref<${JHybridTSpec}::javaobject> view, + jni::alias_ref stateWrapper); + +public: + static void registerNatives() { + registerHybrid({ + makeNativeMethod("updateViewProps", J${stateUpdaterName}::updateViewProps), + }); + } +}; + +} // namespace ${cxxNamespace} + +#endif + `.trim() + + const propsUpdaterCalls = spec.properties.map((p) => { + const name = escapeCppName(p.name) + return ` +if (props.${name}.isDirty) { + view->${p.cppSetterName}(props.${name}.value); +} + `.trim() + }) + const updaterJniCppCode = ` +${createFileMetadataString(`J${stateUpdaterName}.cpp`)} + +#if REACT_NATIVE_VERSION >= 78 + +#include "J${stateUpdaterName}.hpp" +#include "views/${component}.hpp" + +namespace ${cxxNamespace} { + +using namespace facebook; +using ConcreteStateData = react::ConcreteState<${stateClassName}>; + +void J${stateUpdaterName}::updateViewProps(jni::alias_ref, + jni::alias_ref<${JHybridTSpec}::javaobject> javaView, + jni::alias_ref stateWrapper) { + ${JHybridTSpec}* view = javaView->cthis(); + const react::State& state = stateWrapper->cthis(); + // TODO: Can this be a static_cast? + const auto& concreteState = dynamic_cast(state); + const ${stateClassName}& data = concreteState.getData(); + const std::optional<${propsClassName}>& maybeProps = data.getProps(); + if (!maybeProps.has_value()) { + // Props aren't set yet! + throw std::runtime_error("${stateClassName}'s data doesn't contain any props!"); + } + const ${propsClassName}& props = maybeProps.value(); + ${indent(propsUpdaterCalls.join('\n'), ' ')} +} + +} // namespace ${cxxNamespace} + +#endif +`.trim() + + addJNINativeRegistration({ + namespace: cxxNamespace, + className: `J${stateUpdaterName}`, + import: { + name: `J${stateUpdaterName}.hpp`, + space: 'user', + language: 'c++', + }, + }) return [ ...cppFiles, { - content: mmFile, + content: viewManagerCode, language: 'kotlin', name: `${manager}.kt`, platform: 'android', - subdirectory: [...javaNamespace.split('.')], + subdirectory: [...javaSubNamespace.split('.')], + }, + { + content: updaterKotlinCode, + language: 'kotlin', + name: `${stateUpdaterName}.kt`, + platform: 'android', + subdirectory: [...javaSubNamespace.split('.')], + }, + { + content: updaterJniHeaderCode, + language: 'c++', + name: `J${stateUpdaterName}.hpp`, + platform: 'android', + subdirectory: ['views'], + }, + { + content: updaterJniCppCode, + language: 'c++', + name: `J${stateUpdaterName}.cpp`, + platform: 'android', + subdirectory: ['views'], }, ] } diff --git a/packages/react-native-nitro-image/nitrogen/generated/android/NitroImage+autolinking.cmake b/packages/react-native-nitro-image/nitrogen/generated/android/NitroImage+autolinking.cmake index 469efd401..67275e91b 100644 --- a/packages/react-native-nitro-image/nitrogen/generated/android/NitroImage+autolinking.cmake +++ b/packages/react-native-nitro-image/nitrogen/generated/android/NitroImage+autolinking.cmake @@ -42,6 +42,7 @@ target_sources( ../nitrogen/generated/android/c++/JHybridBaseSpec.cpp ../nitrogen/generated/android/c++/JHybridChildSpec.cpp ../nitrogen/generated/android/c++/JHybridTestViewSpec.cpp + ../nitrogen/generated/android/c++/views/JHybridTestViewStateUpdater.cpp ) # Define a flag to check if we are building properly diff --git a/packages/react-native-nitro-image/nitrogen/generated/android/NitroImageOnLoad.cpp b/packages/react-native-nitro-image/nitrogen/generated/android/NitroImageOnLoad.cpp index a7bc4c2d2..dd53e5113 100644 --- a/packages/react-native-nitro-image/nitrogen/generated/android/NitroImageOnLoad.cpp +++ b/packages/react-native-nitro-image/nitrogen/generated/android/NitroImageOnLoad.cpp @@ -30,6 +30,7 @@ #include "JHybridBaseSpec.hpp" #include "JHybridChildSpec.hpp" #include "JHybridTestViewSpec.hpp" +#include "JHybridTestViewStateUpdater.hpp" #include #include #include "HybridTestObjectCpp.hpp" @@ -58,6 +59,7 @@ int initialize(JavaVM* vm) { margelo::nitro::image::JHybridBaseSpec::registerNatives(); margelo::nitro::image::JHybridChildSpec::registerNatives(); margelo::nitro::image::JHybridTestViewSpec::registerNatives(); + margelo::nitro::image::views::JHybridTestViewStateUpdater::registerNatives(); // Register Nitro Hybrid Objects HybridObjectRegistry::registerHybridObjectConstructor( diff --git a/packages/react-native-nitro-image/nitrogen/generated/android/c++/views/JHybridTestViewStateUpdater.cpp b/packages/react-native-nitro-image/nitrogen/generated/android/c++/views/JHybridTestViewStateUpdater.cpp new file mode 100644 index 000000000..6f373c30c --- /dev/null +++ b/packages/react-native-nitro-image/nitrogen/generated/android/c++/views/JHybridTestViewStateUpdater.cpp @@ -0,0 +1,42 @@ +/// +/// JHybridTestViewStateUpdater.cpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#if REACT_NATIVE_VERSION >= 78 + +#include "JHybridTestViewStateUpdater.hpp" +#include "views/HybridTestViewComponent.hpp" + +namespace margelo::nitro::image::views { + +using namespace facebook; +using ConcreteStateData = react::ConcreteState; + +void JHybridTestViewStateUpdater::updateViewProps(jni::alias_ref, + jni::alias_ref javaView, + jni::alias_ref stateWrapper) { + JHybridTestViewSpec* view = javaView->cthis(); + const react::State& state = stateWrapper->cthis(); + // TODO: Can this be a static_cast? + const auto& concreteState = dynamic_cast(state); + const HybridTestViewState& data = concreteState.getData(); + const std::optional& maybeProps = data.getProps(); + if (!maybeProps.has_value()) { + // Props aren't set yet! + throw std::runtime_error("HybridTestViewState's data doesn't contain any props!"); + } + const HybridTestViewProps& props = maybeProps.value(); + if (props.someProp.isDirty) { + view->setSomeProp(props.someProp.value); + } + if (props.someCallback.isDirty) { + view->setSomeCallback(props.someCallback.value); + } +} + +} // namespace margelo::nitro::image::views + +#endif diff --git a/packages/react-native-nitro-image/nitrogen/generated/android/c++/views/JHybridTestViewStateUpdater.hpp b/packages/react-native-nitro-image/nitrogen/generated/android/c++/views/JHybridTestViewStateUpdater.hpp new file mode 100644 index 000000000..0ee287048 --- /dev/null +++ b/packages/react-native-nitro-image/nitrogen/generated/android/c++/views/JHybridTestViewStateUpdater.hpp @@ -0,0 +1,39 @@ +/// +/// JHybridTestViewStateUpdater.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#pragma once + +#if REACT_NATIVE_VERSION >= 78 + +#include +#include +#include "JHybridTestViewSpec.hpp" + +namespace margelo::nitro::image::views { + +using namespace facebook; + +class JHybridTestViewStateUpdater: jni::HybridClass { +public: + static constexpr auto kJavaDescriptor = "Lcom.margelo.nitro.image.views.HybridTestViewStateUpdater;"; + +public: + static void updateViewProps(jni::alias_ref, + jni::alias_ref view, + jni::alias_ref stateWrapper); + +public: + static void registerNatives() { + registerHybrid({ + makeNativeMethod("updateViewProps", JHybridTestViewStateUpdater::updateViewProps), + }); + } +}; + +} // namespace margelo::nitro::image::views + +#endif diff --git a/packages/react-native-nitro-image/nitrogen/generated/android/kotlin/com/margelo/nitro/image/views/HybridTestViewManager.kt b/packages/react-native-nitro-image/nitrogen/generated/android/kotlin/com/margelo/nitro/image/views/HybridTestViewManager.kt index 93d43d14c..09f9637b4 100644 --- a/packages/react-native-nitro-image/nitrogen/generated/android/kotlin/com/margelo/nitro/image/views/HybridTestViewManager.kt +++ b/packages/react-native-nitro-image/nitrogen/generated/android/kotlin/com/margelo/nitro/image/views/HybridTestViewManager.kt @@ -40,7 +40,8 @@ class HybridTestViewManager: SimpleViewManager() { override fun updateState(view: View, props: ReactStylesDiffMap, stateWrapper: StateWrapper): Any? { val stateWrapperImpl = stateWrapper as? StateWrapperImpl ?: throw Error("StateWrapper uses a different implementation!") val hybridView = views[view] ?: throw Error("Couldn't find view $view in local views table!") - // TODO: Get props from stateWrapperImpl and update them in HybridView + + HybridTestViewStateUpdater.updateViewProps(hybridView, stateWrapperImpl) return super.updateState(view, props, stateWrapper) } diff --git a/packages/react-native-nitro-image/nitrogen/generated/android/kotlin/com/margelo/nitro/image/views/HybridTestViewStateUpdater.kt b/packages/react-native-nitro-image/nitrogen/generated/android/kotlin/com/margelo/nitro/image/views/HybridTestViewStateUpdater.kt new file mode 100644 index 000000000..e94ed9774 --- /dev/null +++ b/packages/react-native-nitro-image/nitrogen/generated/android/kotlin/com/margelo/nitro/image/views/HybridTestViewStateUpdater.kt @@ -0,0 +1,23 @@ +/// +/// HybridTestViewStateUpdater.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.image.views + +import com.facebook.react.fabric.StateWrapperImpl +import com.margelo.nitro.image.* + +class HybridTestViewStateUpdater { + companion object { + /** + * Updates the props for [view] through C++. + * The [state] prop is expected to contain [view]'s props as wrapped Fabric state. + */ + @Suppress("KotlinJniMissingFunction") + @JvmStatic + external fun updateViewProps(view: HybridTestView, state: StateWrapperImpl) + } +} diff --git a/packages/react-native-nitro-modules/android/CMakeLists.txt b/packages/react-native-nitro-modules/android/CMakeLists.txt index 8a0226d31..e60d9f722 100644 --- a/packages/react-native-nitro-modules/android/CMakeLists.txt +++ b/packages/react-native-nitro-modules/android/CMakeLists.txt @@ -38,6 +38,7 @@ include_directories( ../cpp/threading ../cpp/turbomodule ../cpp/utils + ../cpp/views # Android-specific C++ includes src/main/cpp/core src/main/cpp/registry diff --git a/packages/react-native-nitro-modules/cpp/views/CachedProp.hpp b/packages/react-native-nitro-modules/cpp/views/CachedProp.hpp index 6a6548ee3..e1caa9087 100644 --- a/packages/react-native-nitro-modules/cpp/views/CachedProp.hpp +++ b/packages/react-native-nitro-modules/cpp/views/CachedProp.hpp @@ -18,7 +18,7 @@ struct CachedProp { public: T value; OwningReference jsiValue; - bool isDirty; + bool isDirty = false; public: bool equals(jsi::Runtime& runtime, const jsi::Value& other) const {