Skip to content

Commit

Permalink
feat: Generate Kotlin/JNI code for this
Browse files Browse the repository at this point in the history
  • Loading branch information
mrousavy committed Jan 20, 2025
1 parent 8e56d0b commit 8828679
Show file tree
Hide file tree
Showing 9 changed files with 270 additions and 12 deletions.
169 changes: 159 additions & 10 deletions packages/nitrogen/src/views/kotlin/KotlinHybridViewManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -21,18 +32,18 @@ export function createKotlinHybridViewManager(
)
}

const mmFile = `
const viewManagerCode = `
${createFileMetadataString(`${manager}.kt`)}
package ${javaNamespace}
package ${javaSubNamespace}
import android.view.View
import com.facebook.react.fabric.StateWrapperImpl
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.
Expand All @@ -59,21 +70,159 @@ class ${manager}: SimpleViewManager<View>() {
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 <fbjni/fbjni.h>
#include <react/fabric/StateWrapperImpl.h>
#include "${JHybridTSpec}.hpp"
namespace ${cxxNamespace} {
using namespace facebook;
class J${stateUpdaterName}: jni::HybridClass<J${stateUpdaterName}> {
public:
static constexpr auto kJavaDescriptor = "L${updaterJniDescriptor};";
public:
static void updateViewProps(jni::alias_ref<jni::JClass>,
jni::alias_ref<${JHybridTSpec}::javaobject> view,
jni::alias_ref<react::StateWrapperImpl::javaobject> 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::JClass>,
jni::alias_ref<${JHybridTSpec}::javaobject> javaView,
jni::alias_ref<react::StateWrapperImpl::javaobject> stateWrapper) {
${JHybridTSpec}* view = javaView->cthis();
const react::State& state = stateWrapper->cthis();
// TODO: Can this be a static_cast?
const auto& concreteState = dynamic_cast<const ConcreteStateData&>(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'],
},
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include "JHybridBaseSpec.hpp"
#include "JHybridChildSpec.hpp"
#include "JHybridTestViewSpec.hpp"
#include "JHybridTestViewStateUpdater.hpp"
#include <NitroModules/JNISharedPtr.hpp>
#include <NitroModules/DefaultConstructableObject.hpp>
#include "HybridTestObjectCpp.hpp"
Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
@@ -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<HybridTestViewState>;

void JHybridTestViewStateUpdater::updateViewProps(jni::alias_ref<jni::JClass>,
jni::alias_ref<JHybridTestViewSpec::javaobject> javaView,
jni::alias_ref<react::StateWrapperImpl::javaobject> stateWrapper) {
JHybridTestViewSpec* view = javaView->cthis();
const react::State& state = stateWrapper->cthis();
// TODO: Can this be a static_cast?
const auto& concreteState = dynamic_cast<const ConcreteStateData&>(state);
const HybridTestViewState& data = concreteState.getData();
const std::optional<HybridTestViewProps>& 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
Original file line number Diff line number Diff line change
@@ -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 <fbjni/fbjni.h>
#include <react/fabric/StateWrapperImpl.h>
#include "JHybridTestViewSpec.hpp"

namespace margelo::nitro::image::views {

using namespace facebook;

class JHybridTestViewStateUpdater: jni::HybridClass<JHybridTestViewStateUpdater> {
public:
static constexpr auto kJavaDescriptor = "Lcom.margelo.nitro.image.views.HybridTestViewStateUpdater;";

public:
static void updateViewProps(jni::alias_ref<jni::JClass>,
jni::alias_ref<JHybridTestViewSpec::javaobject> view,
jni::alias_ref<react::StateWrapperImpl::javaobject> stateWrapper);

public:
static void registerNatives() {
registerHybrid({
makeNativeMethod("updateViewProps", JHybridTestViewStateUpdater::updateViewProps),
});
}
};

} // namespace margelo::nitro::image::views

#endif
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ class HybridTestViewManager: SimpleViewManager<View>() {
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)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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)
}
}
1 change: 1 addition & 0 deletions packages/react-native-nitro-modules/android/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ struct CachedProp {
public:
T value;
OwningReference<jsi::Value> jsiValue;
bool isDirty;
bool isDirty = false;

public:
bool equals(jsi::Runtime& runtime, const jsi::Value& other) const {
Expand Down

0 comments on commit 8828679

Please sign in to comment.