nitro-codegen
Version:
The code-generator for react-native-nitro-modules.
262 lines (226 loc) • 8.46 kB
text/typescript
import type { SourceFile } from '../../syntax/SourceFile.js'
import type { HybridObjectSpec } from '../../syntax/HybridObjectSpec.js'
import {
createViewComponentShadowNodeFiles,
getViewComponentNames,
} from '../CppHybridViewComponent.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 javaSubNamespace = NitroConfig.getAndroidPackage('java/kotlin', 'views')
const javaNamespace = NitroConfig.getAndroidPackage('java/kotlin')
const cxxNamespace = NitroConfig.getCxxNamespace('c++', 'views')
const { JHybridTSpec, HybridTSpec } = getHybridObjectName(spec.name)
const {
manager,
stateClassName,
component,
propsClassName,
descriptorClassName,
} = getViewComponentNames(spec)
const stateUpdaterName = `${stateClassName}Updater`
const autolinking = NitroConfig.getAutolinkedHybridObjects()
const viewImplementation = autolinking[spec.name]?.kotlin
if (viewImplementation == null) {
throw new Error(
`Cannot create Kotlin HybridView ViewManager for ${spec.name} - it is not autolinked in nitro.json!`
)
}
const viewManagerCode = `
${createFileMetadataString(`${manager}.kt`)}
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 ${javaNamespace}.*
/**
* Represents the React Native \`ViewManager\` for the "${spec.name}" Nitro HybridView.
*/
class ${manager}: SimpleViewManager<View>() {
private val views = hashMapOf<View, ${viewImplementation}>()
override fun getName(): String {
return "${spec.name}"
}
override fun createViewInstance(reactContext: ThemedReactContext): View {
val hybridView = ${viewImplementation}(reactContext)
val view = hybridView.view
views[view] = hybridView
return view
}
override fun onDropViewInstance(view: View) {
super.onDropViewInstance(view)
views.remove(view)
}
override fun updateState(view: View, props: ReactStylesDiffMap, stateWrapper: StateWrapper): Any? {
val hybridView = views[view] ?: throw Error("Couldn't find view $view in local views table!")
// 1. Update each prop individually
hybridView.beforeUpdate()
${stateUpdaterName}.updateViewProps(hybridView, stateWrapper)
hybridView.afterUpdate()
// 2. Continue in base View props
return super.updateState(view, props, stateWrapper)
}
}
`.trim()
const updaterKotlinCode = `
${createFileMetadataString(`${stateUpdaterName}.kt`)}
package ${javaSubNamespace}
import com.facebook.react.uimanager.StateWrapper
import ${javaNamespace}.*
internal 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.
*/
external fun updateViewProps(view: ${HybridTSpec}, state: StateWrapper)
}
}
`.trim()
const updaterJniDescriptor = NitroConfig.getAndroidPackage(
'c++/jni',
'views',
stateUpdaterName
)
const updaterJniHeaderCode = `
${createFileMetadataString(`J${stateUpdaterName}.hpp`)}
#pragma once
#include <fbjni/fbjni.h>
#include <react/fabric/StateWrapperImpl.h>
#include <react/fabric/CoreComponentsRegistry.h>
#include <react/renderer/core/ConcreteComponentDescriptor.h>
#include <NitroModules/NitroDefines.hpp>
#include <NitroModules/JStateWrapper.hpp>
#include "${JHybridTSpec}.hpp"
#include "views/${component}.hpp"
namespace ${cxxNamespace} {
using namespace facebook;
class J${stateUpdaterName}: public jni::JavaClass<J${stateUpdaterName}> {
public:
static constexpr auto kJavaDescriptor = "L${updaterJniDescriptor};";
public:
static void updateViewProps(jni::alias_ref<jni::JClass> /* class */,
jni::alias_ref<${JHybridTSpec}::javaobject> view,
jni::alias_ref<JStateWrapper::javaobject> stateWrapperInterface);
public:
static void registerNatives() {
// Register JNI calls
javaClassStatic()->registerNatives({
makeNativeMethod("updateViewProps", J${stateUpdaterName}::updateViewProps),
});
// Register React Native view component descriptor
auto provider = react::concreteComponentDescriptorProvider<${descriptorClassName}>();
auto providerRegistry = react::CoreComponentsRegistry::sharedProviderRegistry();
providerRegistry->add(provider);
}
};
} // namespace ${cxxNamespace}
`.trim()
const propsUpdaterCalls = spec.properties.map((p) => {
const name = escapeCppName(p.name)
const setter = p.getSetterName('other')
return `
if (props.${name}.isDirty) {
view->${setter}(props.${name}.value);
// TODO: Set isDirty = false
}
`.trim()
})
const updaterJniCppCode = `
${createFileMetadataString(`J${stateUpdaterName}.cpp`)}
#include "J${stateUpdaterName}.hpp"
#include "views/${component}.hpp"
#include <NitroModules/NitroDefines.hpp>
#include <NitroModules/JNISharedPtr.hpp>
namespace ${cxxNamespace} {
using namespace facebook;
using ConcreteStateData = react::ConcreteState<${stateClassName}>;
void J${stateUpdaterName}::updateViewProps(jni::alias_ref<jni::JClass> /* class */,
jni::alias_ref<${JHybridTSpec}::javaobject> javaView,
jni::alias_ref<JStateWrapper::javaobject> stateWrapperInterface) {
${JHybridTSpec}* view = javaView->cthis();
// Get concrete StateWrapperImpl from passed StateWrapper interface object
jobject rawStateWrapper = stateWrapperInterface.get();
if (!stateWrapperInterface->isInstanceOf(react::StateWrapperImpl::javaClassStatic())) {
throw std::runtime_error("StateWrapper is not a StateWrapperImpl");
}
auto stateWrapper = jni::alias_ref<react::StateWrapperImpl::javaobject>{
static_cast<react::StateWrapperImpl::javaobject>(rawStateWrapper)};
std::shared_ptr<const react::State> state = stateWrapper->cthis()->getState();
auto concreteState = std::dynamic_pointer_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'), ' ')}
// Update hybridRef if it changed
if (props.hybridRef.isDirty) {
// hybridRef changed - call it with new this
const auto& maybeFunc = props.hybridRef.value;
if (maybeFunc.has_value()) {
auto shared = JNISharedPtr::make_shared_from_jni<${JHybridTSpec}>(jni::make_global(javaView));
maybeFunc.value()(shared);
}
// TODO: Set isDirty = false
}
}
} // namespace ${cxxNamespace}
`.trim()
addJNINativeRegistration({
namespace: cxxNamespace,
className: `J${stateUpdaterName}`,
import: {
name: `views/J${stateUpdaterName}.hpp`,
space: 'user',
language: 'c++',
},
})
return [
...cppFiles,
{
content: viewManagerCode,
language: 'kotlin',
name: `${manager}.kt`,
platform: 'android',
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'],
},
]
}