mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
7fb3d830be
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/46809 BaseViewManagerInterface isn't adding much value right now. It was added in D16984121 to allow codegen generated ViewManager delegates to apply to view managers which derive from ViewMangager instead of BaseViewManager (if they did some cleverness, to make VM delegate apply to a no-op class, still implementing all of BaseViewManager's methods). All of the cases where that was used have since been moved to `SimpleViewManager`, and `BaseViewManagerAdapter` (needed to wire this together) doesn't exist anymore, so it's not possible to take any advantage of this interface existing. We should remove it, since its existence is a source of error (e.g. it was missing setters for `accessibilityValue` or those related to pointer events), and is more generally confusing for anyone adding to `BaseViewManager` in the future. This is a breaking change, because there are some libraries which vendor a copy of generated ViewManagerDelegate when building against legacy arch to be able to share code normally generated at build time. That means these will need to be updated to maintain compatibility with RN versions of 0.77+ with new arch disabled. This will not effect compatibility of these libraries against the default new arch, and the updated delegate is still compatible with older RN version. ``` sourceSets.main { java { if (!isNewArchitectureEnabled()) { srcDirs += [ "src/paper/java", ] } } } ``` 1. `react-native-picker/picker` 2. `rnmapbox/maps` 3. `react-native-gesture-handler` 4. `react-native-screens` 5. `react-native-svg` 6. `react-native-safe-area-context` 7. `react-native-pdf` Changelog: [Android][Breaking] - Remove BaseViewManagerInterface Reviewed By: cortinico Differential Revision: D63819044 fbshipit-source-id: 7e4935c8e43706b168f0f599a6676e8abfa66937
357 lines
10 KiB
JavaScript
357 lines
10 KiB
JavaScript
/**
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @flow strict
|
|
* @format
|
|
*/
|
|
|
|
'use strict';
|
|
import type {CommandParamTypeAnnotation} from '../../CodegenSchema';
|
|
import type {
|
|
CommandTypeAnnotation,
|
|
ComponentShape,
|
|
NamedShape,
|
|
PropTypeAnnotation,
|
|
SchemaType,
|
|
} from '../../CodegenSchema';
|
|
|
|
const {
|
|
getDelegateJavaClassName,
|
|
getImports,
|
|
getInterfaceJavaClassName,
|
|
toSafeJavaString,
|
|
} = require('./JavaHelpers');
|
|
|
|
// File path -> contents
|
|
type FilesOutput = Map<string, string>;
|
|
|
|
const FileTemplate = ({
|
|
packageName,
|
|
imports,
|
|
className,
|
|
extendClasses,
|
|
interfaceClassName,
|
|
methods,
|
|
}: {
|
|
packageName: string,
|
|
imports: string,
|
|
className: string,
|
|
extendClasses: string,
|
|
interfaceClassName: string,
|
|
methods: string,
|
|
}) => `/**
|
|
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
|
|
*
|
|
* Do not edit this file as changes may cause incorrect behavior and will be lost
|
|
* once the code is regenerated.
|
|
*
|
|
* ${'@'}generated by codegen project: GeneratePropsJavaDelegate.js
|
|
*/
|
|
|
|
package ${packageName};
|
|
|
|
${imports}
|
|
|
|
public class ${className}<T extends ${extendClasses}, U extends BaseViewManager<T, ? extends LayoutShadowNode> & ${interfaceClassName}<T>> extends BaseViewManagerDelegate<T, U> {
|
|
public ${className}(U viewManager) {
|
|
super(viewManager);
|
|
}
|
|
${methods}
|
|
}
|
|
`;
|
|
|
|
const PropSetterTemplate = ({propCases}: {propCases: string}) =>
|
|
`
|
|
@Override
|
|
public void setProperty(T view, String propName, @Nullable Object value) {
|
|
${propCases}
|
|
}
|
|
`.trim();
|
|
|
|
const CommandsTemplate = ({commandCases}: {commandCases: string}) =>
|
|
`
|
|
@Override
|
|
public void receiveCommand(T view, String commandName, @Nullable ReadableArray args) {
|
|
switch (commandName) {
|
|
${commandCases}
|
|
}
|
|
}
|
|
`.trim();
|
|
|
|
function getJavaValueForProp(
|
|
prop: NamedShape<PropTypeAnnotation>,
|
|
componentName: string,
|
|
): string {
|
|
const typeAnnotation = prop.typeAnnotation;
|
|
|
|
switch (typeAnnotation.type) {
|
|
case 'BooleanTypeAnnotation':
|
|
if (typeAnnotation.default === null) {
|
|
return 'value == null ? null : (Boolean) value';
|
|
} else {
|
|
return `value == null ? ${typeAnnotation.default.toString()} : (boolean) value`;
|
|
}
|
|
case 'StringTypeAnnotation':
|
|
const defaultValueString =
|
|
typeAnnotation.default === null
|
|
? 'null'
|
|
: `"${typeAnnotation.default}"`;
|
|
return `value == null ? ${defaultValueString} : (String) value`;
|
|
case 'Int32TypeAnnotation':
|
|
return `value == null ? ${typeAnnotation.default} : ((Double) value).intValue()`;
|
|
case 'DoubleTypeAnnotation':
|
|
if (prop.optional) {
|
|
return `value == null ? ${typeAnnotation.default}f : ((Double) value).doubleValue()`;
|
|
} else {
|
|
return 'value == null ? Double.NaN : ((Double) value).doubleValue()';
|
|
}
|
|
case 'FloatTypeAnnotation':
|
|
if (typeAnnotation.default === null) {
|
|
return 'value == null ? null : ((Double) value).floatValue()';
|
|
} else if (prop.optional) {
|
|
return `value == null ? ${typeAnnotation.default}f : ((Double) value).floatValue()`;
|
|
} else {
|
|
return 'value == null ? Float.NaN : ((Double) value).floatValue()';
|
|
}
|
|
case 'ReservedPropTypeAnnotation':
|
|
switch (typeAnnotation.name) {
|
|
case 'ColorPrimitive':
|
|
return 'ColorPropConverter.getColor(value, view.getContext())';
|
|
case 'ImageSourcePrimitive':
|
|
return '(ReadableMap) value';
|
|
case 'ImageRequestPrimitive':
|
|
return '(ReadableMap) value';
|
|
case 'PointPrimitive':
|
|
return '(ReadableMap) value';
|
|
case 'EdgeInsetsPrimitive':
|
|
return '(ReadableMap) value';
|
|
case 'DimensionPrimitive':
|
|
return 'DimensionPropConverter.getDimension(value)';
|
|
default:
|
|
(typeAnnotation.name: empty);
|
|
throw new Error('Received unknown ReservedPropTypeAnnotation');
|
|
}
|
|
case 'ArrayTypeAnnotation': {
|
|
return '(ReadableArray) value';
|
|
}
|
|
case 'ObjectTypeAnnotation': {
|
|
return '(ReadableMap) value';
|
|
}
|
|
case 'StringEnumTypeAnnotation':
|
|
return '(String) value';
|
|
case 'Int32EnumTypeAnnotation':
|
|
return `value == null ? ${typeAnnotation.default} : ((Double) value).intValue()`;
|
|
case 'MixedTypeAnnotation':
|
|
return 'new DynamicFromObject(value)';
|
|
default:
|
|
(typeAnnotation: empty);
|
|
throw new Error('Received invalid typeAnnotation');
|
|
}
|
|
}
|
|
|
|
function generatePropCasesString(
|
|
component: ComponentShape,
|
|
componentName: string,
|
|
) {
|
|
if (component.props.length === 0) {
|
|
return 'super.setProperty(view, propName, value);';
|
|
}
|
|
|
|
const cases = component.props
|
|
.map(prop => {
|
|
return `case "${prop.name}":
|
|
mViewManager.set${toSafeJavaString(
|
|
prop.name,
|
|
)}(view, ${getJavaValueForProp(prop, componentName)});
|
|
break;`;
|
|
})
|
|
.join('\n' + ' ');
|
|
|
|
return `switch (propName) {
|
|
${cases}
|
|
default:
|
|
super.setProperty(view, propName, value);
|
|
}`;
|
|
}
|
|
|
|
function getCommandArgJavaType(
|
|
param: NamedShape<CommandParamTypeAnnotation>,
|
|
index: number,
|
|
) {
|
|
const {typeAnnotation} = param;
|
|
|
|
switch (typeAnnotation.type) {
|
|
case 'ReservedTypeAnnotation':
|
|
switch (typeAnnotation.name) {
|
|
case 'RootTag':
|
|
return `args.getDouble(${index})`;
|
|
default:
|
|
(typeAnnotation.name: empty);
|
|
throw new Error(`Receieved invalid type: ${typeAnnotation.name}`);
|
|
}
|
|
case 'BooleanTypeAnnotation':
|
|
return `args.getBoolean(${index})`;
|
|
case 'DoubleTypeAnnotation':
|
|
return `args.getDouble(${index})`;
|
|
case 'FloatTypeAnnotation':
|
|
return `(float) args.getDouble(${index})`;
|
|
case 'Int32TypeAnnotation':
|
|
return `args.getInt(${index})`;
|
|
case 'StringTypeAnnotation':
|
|
return `args.getString(${index})`;
|
|
case 'ArrayTypeAnnotation':
|
|
return `args.getArray(${index})`;
|
|
default:
|
|
(typeAnnotation.type: empty);
|
|
throw new Error(`Receieved invalid type: ${typeAnnotation.type}`);
|
|
}
|
|
}
|
|
|
|
function getCommandArguments(
|
|
command: NamedShape<CommandTypeAnnotation>,
|
|
): string {
|
|
return [
|
|
'view',
|
|
...command.typeAnnotation.params.map(getCommandArgJavaType),
|
|
].join(', ');
|
|
}
|
|
|
|
function generateCommandCasesString(
|
|
component: ComponentShape,
|
|
componentName: string,
|
|
) {
|
|
if (component.commands.length === 0) {
|
|
return null;
|
|
}
|
|
|
|
const commandMethods = component.commands
|
|
.map(command => {
|
|
return `case "${command.name}":
|
|
mViewManager.${toSafeJavaString(
|
|
command.name,
|
|
false,
|
|
)}(${getCommandArguments(command)});
|
|
break;`;
|
|
})
|
|
.join('\n' + ' ');
|
|
|
|
return commandMethods;
|
|
}
|
|
|
|
function getClassExtendString(component: ComponentShape): string {
|
|
const extendString = component.extendsProps
|
|
.map(extendProps => {
|
|
switch (extendProps.type) {
|
|
case 'ReactNativeBuiltInType':
|
|
switch (extendProps.knownTypeName) {
|
|
case 'ReactNativeCoreViewProps':
|
|
return 'View';
|
|
default:
|
|
(extendProps.knownTypeName: empty);
|
|
throw new Error('Invalid knownTypeName');
|
|
}
|
|
default:
|
|
(extendProps.type: empty);
|
|
throw new Error('Invalid extended type');
|
|
}
|
|
})
|
|
.join('');
|
|
|
|
return extendString;
|
|
}
|
|
|
|
function getDelegateImports(component: ComponentShape) {
|
|
const imports = getImports(component, 'delegate');
|
|
// The delegate needs ReadableArray for commands always.
|
|
// The interface doesn't always need it
|
|
if (component.commands.length > 0) {
|
|
imports.add('import com.facebook.react.bridge.ReadableArray;');
|
|
}
|
|
imports.add('import androidx.annotation.Nullable;');
|
|
imports.add('import com.facebook.react.uimanager.BaseViewManagerDelegate;');
|
|
imports.add('import com.facebook.react.uimanager.BaseViewManager;');
|
|
imports.add('import com.facebook.react.uimanager.LayoutShadowNode;');
|
|
|
|
return imports;
|
|
}
|
|
|
|
function generateMethods(
|
|
propsString: string,
|
|
commandsString: null | string,
|
|
): string {
|
|
return [
|
|
PropSetterTemplate({propCases: propsString}),
|
|
commandsString != null
|
|
? CommandsTemplate({commandCases: commandsString})
|
|
: '',
|
|
]
|
|
.join('\n\n ')
|
|
.trimRight();
|
|
}
|
|
|
|
module.exports = {
|
|
generate(
|
|
libraryName: string,
|
|
schema: SchemaType,
|
|
packageName?: string,
|
|
assumeNonnull: boolean = false,
|
|
headerPrefix?: string,
|
|
): FilesOutput {
|
|
// TODO: This doesn't support custom package name yet.
|
|
const normalizedPackageName = 'com.facebook.react.viewmanagers';
|
|
const outputDir = `java/${normalizedPackageName.replace(/\./g, '/')}`;
|
|
|
|
const files = new Map<string, string>();
|
|
Object.keys(schema.modules).forEach(moduleName => {
|
|
const module = schema.modules[moduleName];
|
|
if (module.type !== 'Component') {
|
|
return;
|
|
}
|
|
|
|
const {components} = module;
|
|
// No components in this module
|
|
if (components == null) {
|
|
return;
|
|
}
|
|
|
|
return Object.keys(components)
|
|
.filter(componentName => {
|
|
const component = components[componentName];
|
|
return !(
|
|
component.excludedPlatforms &&
|
|
component.excludedPlatforms.includes('android')
|
|
);
|
|
})
|
|
.forEach(componentName => {
|
|
const component = components[componentName];
|
|
const className = getDelegateJavaClassName(componentName);
|
|
const interfaceClassName = getInterfaceJavaClassName(componentName);
|
|
|
|
const imports = getDelegateImports(component);
|
|
const propsString = generatePropCasesString(component, componentName);
|
|
const commandsString = generateCommandCasesString(
|
|
component,
|
|
componentName,
|
|
);
|
|
const extendString = getClassExtendString(component);
|
|
|
|
const replacedTemplate = FileTemplate({
|
|
imports: Array.from(imports).sort().join('\n'),
|
|
packageName: normalizedPackageName,
|
|
className,
|
|
extendClasses: extendString,
|
|
methods: generateMethods(propsString, commandsString),
|
|
interfaceClassName: interfaceClassName,
|
|
});
|
|
|
|
files.set(`${outputDir}/${className}.java`, replacedTemplate);
|
|
});
|
|
});
|
|
|
|
return files;
|
|
},
|
|
};
|