Skip to content

Commit

Permalink
Add paperTopLevelNameDeprecated
Browse files Browse the repository at this point in the history
Summary:
This diff adds a way for the codegen to handle view configs with non-standard top event names by adding a `paperTopLevelNameDeprecated` field to events in the schema.

## The problem
The problem this is solving is that Android host components build their own options for event settings in the view config. So instead of enforcing `onChange` to use the top level event name `topChange` like iOS does, Android can use `change` or `forbarChange` or anything the person adding the component wanted at the time:

```
// Expected
topChange: {
  registrationName: 'onChange',
},

// Android
bringBackStargateYouCowards: {
  registrationName: 'onChange',
},
```

This is possible because of the way Android builds these settings
```
// On iOS, notice that there's no option to change the top level name:
RCT_EXPORT_VIEW_PROPERTY(onChange, RCTDirectEventBlock);

// On Android, you provide the top level event name
Override
public Map getExportedCustomDirectEventTypeConstants() {
  return MapBuilder.of(
      "bringBackStargateYouCowards",
      MapBuilder.of("registrationName", "onChange"));
}
```

Since the codegen does not allow us to specify the top level event name (similar to iOS), we don't have a way to customize the names to support android

## The solution
To fix this, we're adding an extra option the event flow types:

```
onBubblingChange: (event: BubblingEvent<Event, 'customBubblingName'>) => void,
onDirectChange: (event: DirectEvent<Event, 'customDirectName'>) => void,
```

These will register **both** top level names in the view config:

```
{
  directEventTypes: {
     // Notice the same registration name is configured for different top level events
    topChange: {
      registrationName: 'onChange',
    },
    bringBackStargateYouCowards: {
      registrationName: 'onChange',
    },
  }
}
```
This means when either `topChange` or `bringBackStargateYouCowards` fires it will call the onChange listener. **This gives us the flexibility to rename the native event name without breaking backwards compatibility.** Old apps will work when `bringBackStargateYouCowards` is fired, and new apps with an update will work when `topChange` fires.

Note: only the correct name will be generated for Fabric so technically we don't even really need to migrate the paper names and this prop can be deleted when paper is gone.

Reviewed By: cpojer

Differential Revision: D16042065

fbshipit-source-id: 40d076b43ffbbfc6c65c3c19de481d922a2add62
  • Loading branch information
rickhanlonii authored and facebook-github-bot committed Jun 28, 2019
1 parent 1468625 commit 9a84970
Show file tree
Hide file tree
Showing 17 changed files with 2,086 additions and 200 deletions.
6 changes: 4 additions & 2 deletions Libraries/Components/Slider/SliderNativeComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,10 @@ type NativeProps = $ReadOnly<{|

// Events
onChange?: ?(event: BubblingEvent<Event>) => void,
onValueChange?: ?(event: BubblingEvent<Event>) => void,
onSlidingComplete?: ?(event: DirectEvent<Event>) => void,
onValueChange?: ?(event: BubblingEvent<Event, 'paperValueChange'>) => void,
onSlidingComplete?: ?(
event: DirectEvent<Event, 'paperSlidingComplete'>,
) => void,
|}>;

export default codegenNativeComponent<NativeProps>('Slider', {
Expand Down
11 changes: 9 additions & 2 deletions Libraries/Types/CodegenTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,15 @@
import type {SyntheticEvent} from './CoreEventTypes';

// Event types
export type BubblingEvent<T> = SyntheticEvent<T>;
export type DirectEvent<T> = SyntheticEvent<T>;
// We're not using the PaperName, it is only used to codegen view config settings
export type BubblingEvent<
T,
PaperName: string | empty = empty, // eslint-disable-line no-unused-vars
> = SyntheticEvent<T>;
export type DirectEvent<
T,
PaperName: string | empty = empty, // eslint-disable-line no-unused-vars
> = SyntheticEvent<T>;

// Prop types
export type Float = number;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,14 @@ type NativeProps = $ReadOnly<{|
// Events
onChange?: ?(event: BubblingEvent<OnChangeEvent>) => void,
onEventDirect?: ?(event: DirectEvent<OnEventDirect>) => void,
onEventDirectWithPaperName?: ?(
event: DirectEvent<OnEventDirect, 'paperDirectName'>,
) => void,
onOrientationChange?: ?(event: DirectEvent<OnOrientationChangeEvent>) => void,
onEnd?: ?(event: BubblingEvent<null>) => void,
onEventBubblingWithPaperName?: ?(
event: BubblingEvent<null, 'paperBubblingName'>,
) => void,
|}>;

export default codegenNativeComponent<NativeProps>('EventPropsNativeComponent');
1 change: 1 addition & 0 deletions packages/react-native-codegen/src/CodegenSchema.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ export type EventTypeShape = $ReadOnly<{|
name: string,
bubblingType: 'direct' | 'bubble',
optional: boolean,
paperTopLevelNameDeprecated?: string,
typeAnnotation: $ReadOnly<{|
type: 'EventTypeAnnotation',
argument?: $ReadOnly<{|
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,10 @@ function getValidAttributesForEvents(events) {
});
}

function generateBubblingEventInfo(event) {
function generateBubblingEventInfo(event, nameOveride) {
return j.property(
'init',
j.identifier(normalizeInputEventName(event.name)),
j.identifier(nameOveride || normalizeInputEventName(event.name)),
j.objectExpression([
j.property(
'init',
Expand All @@ -149,10 +149,10 @@ function generateBubblingEventInfo(event) {
);
}

function generateDirectEventInfo(event) {
function generateDirectEventInfo(event, nameOveride) {
return j.property(
'init',
j.identifier(normalizeInputEventName(event.name)),
j.identifier(nameOveride || normalizeInputEventName(event.name)),
j.objectExpression([
j.property(
'init',
Expand Down Expand Up @@ -205,7 +205,18 @@ function buildViewConfig(

const bubblingEventNames = component.events
.filter(event => event.bubblingType === 'bubble')
.map(generateBubblingEventInfo);
.reduce((bubblingEvents, event) => {
// We add in the deprecated paper name so that it is in the view config.
// This means either the old event name or the new event name can fire
// and be sent to the listener until the old top level name is removed.
if (event.paperTopLevelNameDeprecated) {
bubblingEvents.push(
generateBubblingEventInfo(event, event.paperTopLevelNameDeprecated),
);
}
bubblingEvents.push(generateBubblingEventInfo(event));
return bubblingEvents;
}, []);

const bubblingEvents =
bubblingEventNames.length > 0
Expand All @@ -218,7 +229,18 @@ function buildViewConfig(

const directEventNames = component.events
.filter(event => event.bubblingType === 'direct')
.map(generateDirectEventInfo);
.reduce((directEvents, event) => {
// We add in the deprecated paper name so that it is in the view config.
// This means either the old event name or the new event name can fire
// and be sent to the listener until the old top level name is removed.
if (event.paperTopLevelNameDeprecated) {
directEvents.push(
generateDirectEventInfo(event, event.paperTopLevelNameDeprecated),
);
}
directEvents.push(generateDirectEventInfo(event));
return directEvents;
}, []);

const directEvents =
directEventNames.length > 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,67 @@ const INTERFACE_ONLY: SchemaType = {
},
};

const EVENTS_WITH_PAPER_NAME: SchemaType = {
modules: {
Switch: {
components: {
InterfaceOnlyComponent: {
interfaceOnly: true,
paperComponentName: 'RCTInterfaceOnlyComponent',
extendsProps: [
{
type: 'ReactNativeBuiltInType',
knownTypeName: 'ReactNativeCoreViewProps',
},
],
events: [
{
name: 'onChange',
paperTopLevelNameDeprecated: 'paperChange',
optional: true,
bubblingType: 'bubble',
typeAnnotation: {
type: 'EventTypeAnnotation',
argument: {
type: 'ObjectTypeAnnotation',
properties: [
{
type: 'BooleanTypeAnnotation',
name: 'value',
optional: false,
},
],
},
},
},
{
name: 'onDire tChange',
paperTopLevelNameDeprecated: 'paperDirectChange',
optional: true,
bubblingType: 'direct',
typeAnnotation: {
type: 'EventTypeAnnotation',
argument: {
type: 'ObjectTypeAnnotation',
properties: [
{
type: 'BooleanTypeAnnotation',
name: 'value',
optional: false,
},
],
},
},
},
],
props: [],
commands: [],
},
},
},
},
};

const BOOLEAN_PROP: SchemaType = {
modules: {
Switch: {
Expand Down Expand Up @@ -923,6 +984,7 @@ module.exports = {
MULTI_NATIVE_PROP,
ENUM_PROP,
EVENT_PROPS,
EVENTS_WITH_PAPER_NAME,
EVENT_NESTED_OBJECT_PROPS,
TWO_COMPONENTS_SAME_FILE,
TWO_COMPONENTS_DIFFERENT_FILES,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,42 @@ void EventsNativeComponentEventEmitter::onEnd() const {
}
`;

exports[`GenerateEventEmitterCpp can generate fixture EVENTS_WITH_PAPER_NAME 1`] = `
Map {
"EventEmitters.cpp" => "
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include <react/components/EVENTS_WITH_PAPER_NAME/EventEmitters.h>
namespace facebook {
namespace react {
void InterfaceOnlyComponentEventEmitter::onChange(InterfaceOnlyComponentOnChangeStruct event) const {
dispatchEvent(\\"change\\", [event=std::move(event)](jsi::Runtime &runtime) {
auto payload = jsi::Object(runtime);
payload.setProperty(runtime, \\"value\\", event.value);
return payload;
});
}
void InterfaceOnlyComponentEventEmitter::onDire tChange(InterfaceOnlyComponentOnDire tChangeStruct event) const {
dispatchEvent(\\"dire tChange\\", [event=std::move(event)](jsi::Runtime &runtime) {
auto payload = jsi::Object(runtime);
payload.setProperty(runtime, \\"value\\", event.value);
return payload;
});
}
} // namespace react
} // namespace facebook
",
}
`;

exports[`GenerateEventEmitterCpp can generate fixture FLOAT_PROPS 1`] = `
Map {
"EventEmitters.cpp" => "
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,45 @@ class EventsNativeComponentEventEmitter : public ViewEventEmitter {
}
`;

exports[`GenerateEventEmitterH can generate fixture EVENTS_WITH_PAPER_NAME 1`] = `
Map {
"EventEmitters.h" => "
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <react/components/view/ViewEventEmitter.h>
namespace facebook {
namespace react {
struct InterfaceOnlyComponentOnChangeStruct {
bool value;
};
struct InterfaceOnlyComponentOnDire tChangeStruct {
bool value;
};
class InterfaceOnlyComponentEventEmitter : public ViewEventEmitter {
public:
using ViewEventEmitter::ViewEventEmitter;
void onChange(InterfaceOnlyComponentOnChangeStruct value) const;
void onDire tChange(InterfaceOnlyComponentOnDire tChangeStruct value) const;
};
} // namespace react
} // namespace facebook
",
}
`;

exports[`GenerateEventEmitterH can generate fixture FLOAT_PROPS 1`] = `
Map {
"EventEmitters.h" => "
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,35 @@ EventsNativeComponentProps::EventsNativeComponentProps(
}
`;

exports[`GeneratePropsCpp can generate fixture EVENTS_WITH_PAPER_NAME 1`] = `
Map {
"Props.cpp" => "
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include <react/components/EVENTS_WITH_PAPER_NAME/Props.h>
#include <react/core/propsConversions.h>
namespace facebook {
namespace react {
InterfaceOnlyComponentProps::InterfaceOnlyComponentProps(
const InterfaceOnlyComponentProps &sourceProps,
const RawProps &rawProps): ViewProps(sourceProps, rawProps)
{}
} // namespace react
} // namespace facebook
",
}
`;

exports[`GeneratePropsCpp can generate fixture FLOAT_PROPS 1`] = `
Map {
"Props.cpp" => "
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,38 @@ class EventsNativeComponentProps final : public ViewProps {
}
`;
exports[`GeneratePropsH can generate fixture EVENTS_WITH_PAPER_NAME 1`] = `
Map {
"Props.h" => "
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <react/components/view/ViewProps.h>
namespace facebook {
namespace react {
class InterfaceOnlyComponentProps final : public ViewProps {
public:
InterfaceOnlyComponentProps() = default;
InterfaceOnlyComponentProps(const InterfaceOnlyComponentProps &sourceProps, const RawProps &rawProps);
#pragma mark - Props
};
} // namespace react
} // namespace facebook
",
}
`;
exports[`GeneratePropsH can generate fixture FLOAT_PROPS 1`] = `
Map {
"Props.h" => "
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,29 @@ namespace react {
extern const char EventsNativeComponentComponentName[] = \\"EventsNativeComponent\\";
} // namespace react
} // namespace facebook
",
}
`;

exports[`GenerateShadowNodeCpp can generate fixture EVENTS_WITH_PAPER_NAME 1`] = `
Map {
"ShadowNodes.cpp" => "
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include <react/components/EVENTS_WITH_PAPER_NAME/ShadowNodes.h>
namespace facebook {
namespace react {
} // namespace react
} // namespace facebook
",
Expand Down
Loading

0 comments on commit 9a84970

Please sign in to comment.