Files
react-native/Libraries/ReactNative/PaperUIManager.js
T
David Vacca f96478778c Ensure iOS Native components are initialized when using JS View configs
Summary:
As part of the initialization of Native View configs, the PaperUIManager.getViewManagerConfig() method calls the native side to lazy initialize iOS Native components. This is necessaary to make ensure that native classes are loaded and initialized before a view is created.
Note that this requirement is only necessary when Fabric is disabled.

As part of JS ViewConfigs, we removed the native call to lazy initialize iOS native components. This causes a crash during the creation of NativeViews when JS ViewConfigs are enabled and Fabric is disabled. The rootcase of the exception is that native classes are not properly initialized when the createView method is executed in iOS.

This diff forces the lazy initialization of iOS Native components when JS View configs are enabled and Fabric is disabled. This new mechanism is executed as part of the creation of views and it's ONLY going to be executed when the user navigates to a NON-FABRIC screen, JS ViewConfigs are enabled and the component is not initialized yet.

The extra cost should be minimal or zero.

changelog: [internal] internal

Reviewed By: fkgozali

Differential Revision: D25387014

fbshipit-source-id: fe3bc42f803a805192b419bfb4b7a6b5b1b71b60
2020-12-07 23:14:02 -08:00

183 lines
5.4 KiB
JavaScript

/**
* 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.
*
* @flow
* @format
*/
'use strict';
const NativeModules = require('../BatchedBridge/NativeModules');
const Platform = require('../Utilities/Platform');
const UIManagerProperties = require('./UIManagerProperties');
const defineLazyObjectProperty = require('../Utilities/defineLazyObjectProperty');
import NativeUIManager from './NativeUIManager';
const viewManagerConfigs = {};
const triedLoadingConfig = new Set();
let NativeUIManagerConstants = {};
let isNativeUIManagerConstantsSet = false;
function getConstants(): Object {
if (!isNativeUIManagerConstantsSet) {
NativeUIManagerConstants = NativeUIManager.getConstants();
isNativeUIManagerConstantsSet = true;
}
return NativeUIManagerConstants;
}
function getViewManagerConfig(viewManagerName: string): any {
if (
viewManagerConfigs[viewManagerName] === undefined &&
NativeUIManager.getConstantsForViewManager
) {
try {
viewManagerConfigs[
viewManagerName
] = NativeUIManager.getConstantsForViewManager(viewManagerName);
} catch (e) {
viewManagerConfigs[viewManagerName] = null;
}
}
const config = viewManagerConfigs[viewManagerName];
if (config) {
return config;
}
// If we're in the Chrome Debugger, let's not even try calling the sync
// method.
if (!global.nativeCallSyncHook) {
return config;
}
if (
NativeUIManager.lazilyLoadView &&
!triedLoadingConfig.has(viewManagerName)
) {
const result = NativeUIManager.lazilyLoadView(viewManagerName);
triedLoadingConfig.add(viewManagerName);
if (result != null && result.viewConfig != null) {
getConstants()[viewManagerName] = result.viewConfig;
lazifyViewManagerConfig(viewManagerName);
}
}
return viewManagerConfigs[viewManagerName];
}
/* $FlowFixMe(>=0.123.0 site=react_native_fb) This comment suppresses an error
* found when Flow v0.123.0 was deployed. To see the error, delete this comment
* and run Flow. */
const UIManagerJS = {
...NativeUIManager,
createView(
reactTag: ?number,
viewName: string,
rootTag: number,
props: Object,
): void {
if (Platform.OS === 'ios' && viewManagerConfigs[viewName] === undefined) {
// This is necessary to force the initialization of native viewManager
// classes in iOS when using static ViewConfigs
getViewManagerConfig(viewName);
}
NativeUIManager.createView(reactTag, viewName, rootTag, props);
},
getConstants(): Object {
return getConstants();
},
getViewManagerConfig(viewManagerName: string): any {
return getViewManagerConfig(viewManagerName);
},
};
// TODO (T45220498): Remove this.
// 3rd party libs may be calling `NativeModules.UIManager.getViewManagerConfig()`
// instead of `UIManager.getViewManagerConfig()` off UIManager.js.
// This is a workaround for now.
// $FlowFixMe
NativeUIManager.getViewManagerConfig = UIManagerJS.getViewManagerConfig;
function lazifyViewManagerConfig(viewName) {
const viewConfig = getConstants()[viewName];
viewManagerConfigs[viewName] = viewConfig;
if (viewConfig.Manager) {
defineLazyObjectProperty(viewConfig, 'Constants', {
get: () => {
const viewManager = NativeModules[viewConfig.Manager];
const constants = {};
viewManager &&
Object.keys(viewManager).forEach(key => {
const value = viewManager[key];
if (typeof value !== 'function') {
constants[key] = value;
}
});
return constants;
},
});
defineLazyObjectProperty(viewConfig, 'Commands', {
get: () => {
const viewManager = NativeModules[viewConfig.Manager];
const commands = {};
let index = 0;
viewManager &&
Object.keys(viewManager).forEach(key => {
const value = viewManager[key];
if (typeof value === 'function') {
commands[key] = index++;
}
});
return commands;
},
});
}
}
/**
* Copies the ViewManager constants and commands into UIManager. This is
* only needed for iOS, which puts the constants in the ViewManager
* namespace instead of UIManager, unlike Android.
*/
if (Platform.OS === 'ios') {
Object.keys(getConstants()).forEach(viewName => {
lazifyViewManagerConfig(viewName);
});
} else if (getConstants().ViewManagerNames) {
NativeUIManager.getConstants().ViewManagerNames.forEach(viewManagerName => {
defineLazyObjectProperty(NativeUIManager, viewManagerName, {
get: () => NativeUIManager.getConstantsForViewManager(viewManagerName),
});
});
}
if (!global.nativeCallSyncHook) {
Object.keys(getConstants()).forEach(viewManagerName => {
if (!UIManagerProperties.includes(viewManagerName)) {
if (!viewManagerConfigs[viewManagerName]) {
viewManagerConfigs[viewManagerName] = getConstants()[viewManagerName];
}
defineLazyObjectProperty(NativeUIManager, viewManagerName, {
get: () => {
console.warn(
`Accessing view manager configs directly off UIManager via UIManager['${viewManagerName}'] ` +
`is no longer supported. Use UIManager.getViewManagerConfig('${viewManagerName}') instead.`,
);
return UIManagerJS.getViewManagerConfig(viewManagerName);
},
});
}
});
}
module.exports = UIManagerJS;