From d0dd1aed29efd938dfaf2c741b4d4034d3019772 Mon Sep 17 00:00:00 2001 From: Oleksandr Melnykov Date: Thu, 3 Oct 2019 03:12:46 -0700 Subject: [PATCH] Integrate AndroidSwitch into Fabric on Android Summary: In this diff we integrate the Switch component on Android in Fabric. Since the component has a custom measure function, we need to write some C++ to call the measure method in Java. The component isn't fully functional yet (setNativeProps isn't supported in Fabric) and has some problems with measuring itself. I will fix the component in the next diffs in this stack. Reviewed By: JoshuaGross Differential Revision: D17571258 fbshipit-source-id: be4e201495b9b197ddec44ee3484357bfb6225a8 --- .../Switch/AndroidSwitchNativeComponent.js | 6 +- .../views/switchview/ReactSwitchManager.java | 19 ++++ ReactCommon/fabric/components/switch/BUCK | 96 +++++++++++++++++++ .../AndroidSwitchComponentDescriptor.h | 53 ++++++++++ .../AndroidSwitchMeasurementsManager.cpp | 65 +++++++++++++ .../AndroidSwitchMeasurementsManager.h | 33 +++++++ .../androidswitch/AndroidSwitchShadowNode.cpp | 30 ++++++ .../androidswitch/AndroidSwitchShadowNode.h | 45 +++++++++ 8 files changed, 344 insertions(+), 3 deletions(-) create mode 100644 ReactCommon/fabric/components/switch/BUCK create mode 100644 ReactCommon/fabric/components/switch/androidswitch/AndroidSwitchComponentDescriptor.h create mode 100644 ReactCommon/fabric/components/switch/androidswitch/AndroidSwitchMeasurementsManager.cpp create mode 100644 ReactCommon/fabric/components/switch/androidswitch/AndroidSwitchMeasurementsManager.h create mode 100644 ReactCommon/fabric/components/switch/androidswitch/AndroidSwitchShadowNode.cpp create mode 100644 ReactCommon/fabric/components/switch/androidswitch/AndroidSwitchShadowNode.h diff --git a/Libraries/Components/Switch/AndroidSwitchNativeComponent.js b/Libraries/Components/Switch/AndroidSwitchNativeComponent.js index 80d1ec86295..5634aa4199d 100644 --- a/Libraries/Components/Switch/AndroidSwitchNativeComponent.js +++ b/Libraries/Components/Switch/AndroidSwitchNativeComponent.js @@ -43,6 +43,6 @@ type NativeProps = $ReadOnly<{| onChange?: BubblingEventHandler, |}>; -export default (codegenNativeComponent( - 'AndroidSwitch', -): HostComponent); +export default (codegenNativeComponent('AndroidSwitch', { + interfaceOnly: true, +}): HostComponent); diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/switchview/ReactSwitchManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/switchview/ReactSwitchManager.java index 7893b8453d7..8164a30a28d 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/switchview/ReactSwitchManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/switchview/ReactSwitchManager.java @@ -8,10 +8,12 @@ // switchview because switch is a keyword package com.facebook.react.views.switchview; +import android.content.Context; import android.view.View; import android.widget.CompoundButton; import androidx.annotation.Nullable; import com.facebook.react.bridge.ReactContext; +import com.facebook.react.bridge.ReadableMap; import com.facebook.react.uimanager.LayoutShadowNode; import com.facebook.react.uimanager.SimpleViewManager; import com.facebook.react.uimanager.ThemedReactContext; @@ -177,4 +179,21 @@ public class ReactSwitchManager extends SimpleViewManager protected ViewManagerDelegate getDelegate() { return mDelegate; } + + @Override + public long measure( + Context context, + ReadableMap localData, + ReadableMap props, + ReadableMap state, + float width, + YogaMeasureMode widthMode, + float height, + YogaMeasureMode heightMode) { + ReactSwitch view = new ReactSwitch(context); + view.setShowText(false); + int measureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); + view.measure(measureSpec, measureSpec); + return YogaMeasureOutput.make(view.getMeasuredWidth(), view.getMeasuredHeight()); + } } diff --git a/ReactCommon/fabric/components/switch/BUCK b/ReactCommon/fabric/components/switch/BUCK new file mode 100644 index 00000000000..8b48dcd58a8 --- /dev/null +++ b/ReactCommon/fabric/components/switch/BUCK @@ -0,0 +1,96 @@ +load("@fbsource//tools/build_defs/apple:flag_defs.bzl", "get_debug_preprocessor_flags") +load( + "//tools/build_defs/oss:rn_defs.bzl", + "ANDROID", + "APPLE", + "CXX", + "YOGA_CXX_TARGET", + "fb_xplat_cxx_test", + "get_apple_compiler_flags", + "get_apple_inspector_flags", + "react_native_target", + "react_native_xplat_target", + "rn_xplat_cxx_library", + "subdir_glob", +) + +APPLE_COMPILER_FLAGS = get_apple_compiler_flags() + +rn_xplat_cxx_library( + name = "androidswitch", + srcs = glob( + ["**/*.cpp"], + exclude = glob(["tests/**/*.cpp"]), + ), + headers = glob( + ["**/*.h"], + exclude = glob(["tests/**/*.h"]), + ), + header_namespace = "", + exported_headers = subdir_glob( + [ + ("", "*.h"), + ("androidswitch", "*.h"), + ], + prefix = "react/components/androidswitch", + ), + compiler_flags = [ + "-fexceptions", + "-frtti", + "-std=c++14", + "-Wall", + ], + cxx_tests = [":tests"], + fbandroid_deps = [ + react_native_target("jni/react/jni:jni"), + ], + fbobjc_compiler_flags = APPLE_COMPILER_FLAGS, + fbobjc_preprocessor_flags = get_debug_preprocessor_flags() + get_apple_inspector_flags(), + force_static = True, + platforms = (ANDROID, APPLE, CXX), + preprocessor_flags = [ + "-DLOG_TAG=\"ReactNative\"", + "-DWITH_FBSYSTRACE=1", + ], + visibility = ["PUBLIC"], + deps = [ + "fbsource//xplat/fbsystrace:fbsystrace", + "fbsource//xplat/folly:headers_only", + "fbsource//xplat/folly:memory", + "fbsource//xplat/folly:molly", + "fbsource//xplat/third-party/glog:glog", + YOGA_CXX_TARGET, + react_native_xplat_target("fabric/debug:debug"), + react_native_xplat_target("fabric/core:core"), + react_native_xplat_target("fabric/graphics:graphics"), + react_native_xplat_target("fabric/components/view:view"), + react_native_xplat_target("fabric/uimanager:uimanager"), + "fbsource//xplat/js/react-native-github:generated_components-rncore", + ], +) + +fb_xplat_cxx_test( + name = "tests", + srcs = glob(["tests/**/*.cpp"]), + headers = glob(["tests/**/*.h"]), + compiler_flags = [ + "-fexceptions", + "-frtti", + "-std=c++14", + "-Wall", + ], + contacts = ["oncall+react_native@xmail.facebook.com"], + platforms = ( + # `Apple` and `Android` flavors are disabled because the module depends on `textlayoutmanager` which requires real an Emulator/Simulator to run. + # At the same time, the code of tests does not rely on the simulator capabilities and it would be wasteful to add `fbandroid_use_instrumentation_test = True`. + # (Beware of this option though.) + # ANDROID, + # APPLE, + CXX + ), + deps = [ + "fbsource//xplat/folly:molly", + "fbsource//xplat/third-party/gmock:gtest", + ":androidswitch", + ], +) diff --git a/ReactCommon/fabric/components/switch/androidswitch/AndroidSwitchComponentDescriptor.h b/ReactCommon/fabric/components/switch/androidswitch/AndroidSwitchComponentDescriptor.h new file mode 100644 index 00000000000..4c6d8dbf206 --- /dev/null +++ b/ReactCommon/fabric/components/switch/androidswitch/AndroidSwitchComponentDescriptor.h @@ -0,0 +1,53 @@ +/** + * 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 "AndroidSwitchMeasurementsManager.h" +#include "AndroidSwitchShadowNode.h" + +#include + +namespace facebook { +namespace react { + +/* + * Descriptor for component. + */ +class AndroidSwitchComponentDescriptor final + : public ConcreteComponentDescriptor { + public: + AndroidSwitchComponentDescriptor( + EventDispatcher::Weak eventDispatcher, + ContextContainer::Shared const &contextContainer) + : ConcreteComponentDescriptor(eventDispatcher), + measurementsManager_(std::make_shared( + contextContainer)) {} + + void adopt(UnsharedShadowNode shadowNode) const override { + ConcreteComponentDescriptor::adopt(shadowNode); + + assert(std::dynamic_pointer_cast(shadowNode)); + auto androidSwitchShadowNode = + std::static_pointer_cast(shadowNode); + + // `AndroidSwitchShadowNode` uses `AndroidSwitchMeasurementsManager` to + // provide measurements to Yoga. + androidSwitchShadowNode->setAndroidSwitchMeasurementsManager( + measurementsManager_); + + // All `AndroidSwitchShadowNode`s must have leaf Yoga nodes with properly + // setup measure function. + androidSwitchShadowNode->enableMeasurement(); + } + + private: + const std::shared_ptr measurementsManager_; +}; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/components/switch/androidswitch/AndroidSwitchMeasurementsManager.cpp b/ReactCommon/fabric/components/switch/androidswitch/AndroidSwitchMeasurementsManager.cpp new file mode 100644 index 00000000000..78df033f476 --- /dev/null +++ b/ReactCommon/fabric/components/switch/androidswitch/AndroidSwitchMeasurementsManager.cpp @@ -0,0 +1,65 @@ +/** + * 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 "AndroidSwitchMeasurementsManager.h" + +#include +#include +#include + +using namespace facebook::jni; + +namespace facebook { +namespace react { + +Size AndroidSwitchMeasurementsManager::measure( + LayoutConstraints layoutConstraints) const { + { + std::lock_guard lock(mutex_); + if (hasBeenMeasured_) { + return cachedMeasurement_; + } + } + + const jni::global_ref &fabricUIManager = + contextContainer_->at>("FabricUIManager"); + + static auto measure = + jni::findClassStatic("com/facebook/react/fabric/FabricUIManager") + ->getMethod("measure"); + + auto minimumSize = layoutConstraints.minimumSize; + auto maximumSize = layoutConstraints.maximumSize; + + local_ref componentName = make_jstring("AndroidSwitch"); + + auto measurement = yogaMeassureToSize(measure( + fabricUIManager, + componentName.get(), + nullptr, + nullptr, + nullptr, + minimumSize.width, + maximumSize.width, + minimumSize.height, + maximumSize.height)); + + std::lock_guard lock(mutex_); + cachedMeasurement_ = measurement; + return measurement; +} + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/components/switch/androidswitch/AndroidSwitchMeasurementsManager.h b/ReactCommon/fabric/components/switch/androidswitch/AndroidSwitchMeasurementsManager.h new file mode 100644 index 00000000000..b0cc5942df8 --- /dev/null +++ b/ReactCommon/fabric/components/switch/androidswitch/AndroidSwitchMeasurementsManager.h @@ -0,0 +1,33 @@ +/** + * 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 +#include +#include + +namespace facebook { +namespace react { + +class AndroidSwitchMeasurementsManager { + public: + AndroidSwitchMeasurementsManager( + const ContextContainer::Shared &contextContainer) + : contextContainer_(contextContainer) {} + + Size measure(LayoutConstraints layoutConstraints) const; + + private: + const ContextContainer::Shared contextContainer_; + mutable std::mutex mutex_; + mutable bool hasBeenMeasured_ = false; + mutable Size cachedMeasurement_{}; +}; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/components/switch/androidswitch/AndroidSwitchShadowNode.cpp b/ReactCommon/fabric/components/switch/androidswitch/AndroidSwitchShadowNode.cpp new file mode 100644 index 00000000000..fe0dc73856f --- /dev/null +++ b/ReactCommon/fabric/components/switch/androidswitch/AndroidSwitchShadowNode.cpp @@ -0,0 +1,30 @@ +/** + * 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 "AndroidSwitchShadowNode.h" + +namespace facebook { +namespace react { + +extern const char AndroidSwitchComponentName[] = "AndroidSwitch"; + +void AndroidSwitchShadowNode::setAndroidSwitchMeasurementsManager( + const std::shared_ptr + &measurementsManager) { + ensureUnsealed(); + measurementsManager_ = measurementsManager; +} + +#pragma mark - LayoutableShadowNode + +Size AndroidSwitchShadowNode::measure( + LayoutConstraints layoutConstraints) const { + return measurementsManager_->measure(layoutConstraints); +} + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/components/switch/androidswitch/AndroidSwitchShadowNode.h b/ReactCommon/fabric/components/switch/androidswitch/AndroidSwitchShadowNode.h new file mode 100644 index 00000000000..dd89ccc6152 --- /dev/null +++ b/ReactCommon/fabric/components/switch/androidswitch/AndroidSwitchShadowNode.h @@ -0,0 +1,45 @@ +/** + * 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 "AndroidSwitchMeasurementsManager.h" + +#include +#include +#include + +namespace facebook { +namespace react { + +extern const char AndroidSwitchComponentName[]; + +/* + * `ShadowNode` for component. + */ +class AndroidSwitchShadowNode final : public ConcreteViewShadowNode< + AndroidSwitchComponentName, + AndroidSwitchProps, + AndroidSwitchEventEmitter> { + public: + using ConcreteViewShadowNode::ConcreteViewShadowNode; + + // Associates a shared `AndroidSwitchMeasurementsManager` with the node. + void setAndroidSwitchMeasurementsManager( + const std::shared_ptr + &measurementsManager); + +#pragma mark - LayoutableShadowNode + + Size measure(LayoutConstraints layoutConstraints) const override; + + private: + std::shared_ptr measurementsManager_; +}; + +} // namespace react +} // namespace facebook