Skip to content

Commit

Permalink
Make UIManager.dispatchViewManagerCommand work in Fabric through Inte…
Browse files Browse the repository at this point in the history
…rop Layer (#36578)

Summary:
Pull Request resolved: #36578

This change brings to the Fabric Interop Layer the possibility to directly call native methods on the View Manager, something that was not possible before and that we can use to simplify the migration to the New Architecture.

## Changelog:
[iOS][Added] - Native Components can now call native methods in Fabric when they are loaded through the Interop Layer

Reviewed By: sammy-SC

Differential Revision: D43945278

fbshipit-source-id: fe67ac85a5d0db3747105f56700d1dbba7ada5f1
  • Loading branch information
cipolleschi authored and facebook-github-bot committed Mar 29, 2023
1 parent 688df2f commit c7aaae6
Show file tree
Hide file tree
Showing 7 changed files with 131 additions and 9 deletions.
27 changes: 27 additions & 0 deletions packages/react-native/Libraries/ReactNative/UIManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,33 @@ const UIManager = {
);
}
},

dispatchViewManagerCommand(
reactTag: number,
commandName: number | string,
commandArgs: any[],
) {
if (isFabricReactTag(reactTag)) {
const FabricUIManager = nullthrows(getFabricUIManager());
const shadowNode =
FabricUIManager.findShadowNodeByTag_DEPRECATED(reactTag);
if (shadowNode) {
// Transform the accidental CommandID into a CommandName which is the stringified number.
// The interop layer knows how to convert this number into the right method name.
// Stringify a string is a no-op, so it's safe.
commandName = `${commandName}`;
FabricUIManager.dispatchCommand(shadowNode, commandName, commandArgs);
}
} else {
UIManagerImpl.dispatchViewManagerCommand(
reactTag,
// We have some legacy components that are actually already using strings. ¯\_(ツ)_/¯
// $FlowFixMe[incompatible-call]
commandName,
commandArgs,
);
}
},
};

module.exports = UIManager;
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ - (void)setProps:(const folly::dynamic &)props

- (void)handleCommand:(NSString *)commandName args:(NSArray *)args
{
[_coordinator handleCommand:commandName args:args reactTag:_tag];
[_coordinator handleCommand:commandName args:args reactTag:_tag paperView:self.paperView];
}

@end
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ typedef void (^InterceptorBlock)(std::string eventName, folly::dynamic event);

- (NSString *)componentViewName;

- (void)handleCommand:(NSString *)commandName args:(NSArray *)args reactTag:(NSInteger)tag;
- (void)handleCommand:(NSString *)commandName
args:(NSArray *)args
reactTag:(NSInteger)tag
paperView:(UIView *)paperView;

@end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,13 +100,23 @@ - (NSString *)componentViewName
return RCTDropReactPrefixes(_componentData.name);
}

- (void)handleCommand:(NSString *)commandName args:(NSArray *)args reactTag:(NSInteger)tag
- (void)handleCommand:(NSString *)commandName
args:(NSArray *)args
reactTag:(NSInteger)tag
paperView:(nonnull UIView *)paperView
{
Class managerClass = _componentData.managerClass;
[self _lookupModuleMethodsIfNecessary];
RCTModuleData *moduleData = [_bridge.batchedBridge moduleDataForName:RCTBridgeModuleNameForClass(managerClass)];
id<RCTBridgeMethod> method;
if ([commandName isKindOfClass:[NSNumber class]]) {

// We can't use `[NSString intValue]` as "0" is a valid command,
// but also a falsy value. [NSNumberFormatter numberFromString] returns a
// `NSNumber *` which is NULL when it's to be NULL
// and it points to 0 when the string is @"0" (not a falsy value).
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];

if ([commandName isKindOfClass:[NSNumber class]] || [formatter numberFromString:commandName] != NULL) {
method = moduleData ? moduleData.methods[[commandName intValue]] : _moduleMethods[[commandName intValue]];
} else if ([commandName isKindOfClass:[NSString class]]) {
method = moduleData ? moduleData.methodsByName[commandName] : _moduleMethodsByName[commandName];
Expand All @@ -121,19 +131,56 @@ - (void)handleCommand:(NSString *)commandName args:(NSArray *)args reactTag:(NSI
NSArray *newArgs = [@[ [NSNumber numberWithInteger:tag] ] arrayByAddingObjectsFromArray:args];

if (_bridge) {
[self _addViewToRegistry:paperView withTag:tag];
[_bridge.batchedBridge
dispatchBlock:^{
[method invokeWithBridge:self->_bridge module:self->_componentData.manager arguments:newArgs];
[self->_bridge.uiManager setNeedsLayout];
}
queue:RCTGetUIManagerQueue()];
[self _removeViewFromRegistryWithTag:tag];
} else {
// TODO T86826778 - Figure out which queue this should be dispatched to.
[method invokeWithBridge:nil module:self->_componentData.manager arguments:newArgs];
}
}

#pragma mark - Private
- (void)_addViewToRegistry:(UIView *)view withTag:(NSInteger)tag
{
[self _addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
if ([viewRegistry objectForKey:@(tag)] != NULL) {
return;
}
NSMutableDictionary<NSNumber *, UIView *> *mutableViewRegistry =
(NSMutableDictionary<NSNumber *, UIView *> *)viewRegistry;
[mutableViewRegistry setObject:view forKey:@(tag)];
}];
}

- (void)_removeViewFromRegistryWithTag:(NSInteger)tag
{
[self _addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
if ([viewRegistry objectForKey:@(tag)] == NULL) {
return;
}

NSMutableDictionary<NSNumber *, UIView *> *mutableViewRegistry =
(NSMutableDictionary<NSNumber *, UIView *> *)viewRegistry;
[mutableViewRegistry removeObjectForKey:@(tag)];
}];
}

- (void)_addUIBlock:(RCTViewManagerUIBlock)block
{
__weak __typeof__(self) weakSelf = self;
[_bridge.batchedBridge
dispatchBlock:^{
__typeof__(self) strongSelf = weakSelf;
[strongSelf->_bridge.uiManager addUIBlock:block];
}
queue:RCTGetUIManagerQueue()];
}

// This is copy-pasta from RCTModuleData.
- (void)_lookupModuleMethodsIfNecessary
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,34 @@ + (BOOL)requiresMainQueueSetup

RCT_EXPORT_VIEW_PROPERTY(onColorChanged, RCTBubblingEventBlock)

RCT_EXPORT_METHOD(changeBackgroundColor : (nonnull NSNumber *)reactTag color : (NSString *)color)
{
[self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
UIView *view = viewRegistry[reactTag];
if (!view || ![view isKindOfClass:[RNTLegacyView class]]) {
RCTLogError(@"Cannot find RNTLegacyView with tag #%@", reactTag);
return;
}

unsigned rgbValue = 0;
NSString *colorString = [NSString stringWithCString:std::string([color UTF8String]).c_str()
encoding:[NSString defaultCStringEncoding]];
NSScanner *scanner = [NSScanner scannerWithString:colorString];
[scanner setScanLocation:1]; // bypass '#' character
[scanner scanHexInt:&rgbValue];

UIColor *newColor = [UIColor colorWithRed:((rgbValue & 0xFF0000) >> 16) / 255.0
green:((rgbValue & 0xFF00) >> 8) / 255.0
blue:(rgbValue & 0xFF) / 255.0
alpha:1.0];
view.backgroundColor = newColor;
}];
}

- (UIView *)view
{
RNTLegacyView *view = [[RNTLegacyView alloc] init];
view.backgroundColor = UIColor.redColor;
return view;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@
* @format
*/

import * as React from 'react';
import type {HostComponent} from 'react-native';
import type {ViewProps} from 'react-native/Libraries/Components/View/ViewPropTypes';
import {requireNativeComponent} from 'react-native';
import {requireNativeComponent, UIManager} from 'react-native';
import ReactNative from '../../../react-native/Libraries/Renderer/shims/ReactNative';

type ColorChangedEvent = {
nativeEvent: {
Expand All @@ -32,6 +34,22 @@ type NativeProps = $ReadOnly<{|

export type MyLegacyViewType = HostComponent<NativeProps>;

export function callNativeMethodToChangeBackgroundColor(
viewRef: React.ElementRef<MyLegacyViewType> | null,
color: string,
) {
if (!viewRef) {
console.log('viewRef is null');
return;
}
UIManager.dispatchViewManagerCommand(
ReactNative.findNodeHandle(viewRef),
UIManager.getViewManagerConfig('RNTMyLegacyNativeView').Commands
.changeBackgroundColor,
[color],
);
}

export default (requireNativeComponent(
'RNTMyLegacyNativeView',
): HostComponent<NativeProps>);
10 changes: 6 additions & 4 deletions packages/rn-tester/NativeComponentExample/js/MyNativeView.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ import RNTMyNativeView, {
Commands as RNTMyNativeViewCommands,
} from './MyNativeViewNativeComponent';
import RNTMyLegacyNativeView from './MyLegacyViewNativeComponent';
import type {MyLegacyViewType} from './MyLegacyViewNativeComponent';
import type {MyNativeViewType} from './MyNativeViewNativeComponent';

import {callNativeMethodToChangeBackgroundColor} from './MyLegacyViewNativeComponent';
const colors = [
'#0000FF',
'#FF0000',
Expand Down Expand Up @@ -52,18 +53,18 @@ class HSBA {
// This is an example component that migrates to use the new architecture.
export default function MyNativeView(props: {}): React.Node {
const ref = useRef<React.ElementRef<MyNativeViewType> | null>(null);
const legacyRef = useRef<React.ElementRef<MyLegacyViewType> | null>(null);
const [opacity, setOpacity] = useState(1.0);
const [color, setColor] = useState('#FF0000');
const [hsba, setHsba] = useState<HSBA>(new HSBA());
return (
<View style={{flex: 1}}>
<Text style={{color: 'red'}}>Fabric View</Text>
<RNTMyNativeView ref={ref} style={{flex: 1}} opacity={opacity} />
<Text style={{color: 'red'}}>Legacy View</Text>
<RNTMyLegacyNativeView
ref={legacyRef}
style={{flex: 1}}
opacity={opacity}
color={color}
onColorChanged={event =>
setHsba(
new HSBA(
Expand All @@ -84,12 +85,13 @@ export default function MyNativeView(props: {}): React.Node {
title="Change Background"
onPress={() => {
let newColor = colors[Math.floor(Math.random() * 5)];
setColor(newColor);
RNTMyNativeViewCommands.callNativeMethodToChangeBackgroundColor(
// $FlowFixMe[incompatible-call]
ref.current,
newColor,
);

callNativeMethodToChangeBackgroundColor(legacyRef.current, newColor);
}}
/>
<Button
Expand Down

0 comments on commit c7aaae6

Please sign in to comment.