Skip to content

Commit 06e2db9

Browse files
SosenWiosenhuangyouhua
authored andcommitted
Fix: KeyboardAvoidingView jitters in modals without set height
Co-authored-by: Przemysław Sosna<przemyslaw.sosna@swmansion.com> # message auto-generated for no-merge-commit merge: merge @sosen/modal-host-view-state into 0.77.1-rc.1-ohos Fix: KeyboardAvoidingView jitters in modals without set height Created-by: PrzemekSosna Commit-by: Przemysław Sosna Merged-by: huangyouhua Description: ## Description ModalHostViewState was previously initialized with height and width {0,0}. This would cause the first onLayout events to return the wrong height. KeyboardAvoidingView uses the first onLayout to set initialFrameHeight, which is then used to calculate the height of the components. This would lead to negative values being calculated for the height causing jittering etc. This is the PR in the core RN repo for reference: facebook/react-native#51048 ## Changes - fixed KeyboardAvoidingView jittering by initializing ModalHostViewState with sensible values. ## Test Plan 1. Run the below snippet and focus the TextInput. You shouldn't see any jittering. ```ts import React, {useState} from 'react'; import { View, Text, TextInput, Pressable, StyleSheet, Modal, KeyboardAvoidingView, } from 'react-native'; const App = () => { const [modalOpen, setModalOpen] = useState(false); return ( <View style={styles.outerContainer} onLayout={layoutEvent => { console.log('Outer container layout:', layoutEvent.nativeEvent.layout); }}> <Modal animationType="fade" visible={modalOpen} onLayout={layoutEvent => { console.log('modal layout:', layoutEvent.nativeEvent.layout); }}> <KeyboardAvoidingView behavior="height" style={styles.container}> <View style={[styles.closeView, {marginHorizontal: 25}]}> <Pressable onPress={() => setModalOpen(false)} style={styles.closeButton}> <Text>Close</Text> </Pressable> </View> <TextInput placeholder="TextInput" style={styles.textInput} /> </KeyboardAvoidingView> </Modal> <Pressable onPress={() => setModalOpen(true)} style={styles.launchButton}> <Text>Open Example</Text> </Pressable> </View> ); }; const styles = StyleSheet.create({ outerContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: 'cyan', }, container: { flex: 1, justifyContent: 'center', paddingHorizontal: 20, paddingTop: 20, backgroundColor: 'red', alignItems: 'center', }, textInput: { borderRadius: 5, borderWidth: 1, height: 44, width: 300, paddingHorizontal: 10, }, closeView: { alignSelf: 'stretch', }, closeButton: { alignSelf: 'flex-end', }, launchButton: { padding: 10, backgroundColor: '#eee', borderRadius: 5, }, }); export default App; ``` ## Checklist - [x] Does not involve incompatible changes; if involved, has been reviewed accordingly. - [x] Does not impact performance, or performance testing has been conducted without degradation. - [x] Complies with the relevant coding standards. - [x] Does not involve documentation updates, or the documentation has been updated. - [x] Meets testability requirements with necessary self-test cases, appropriate logging, or trace information added. - [x] No illegal file inclusions exist, such as images or code. See merge request: openharmony-sig/ohos_react_native!1184
1 parent cc425f6 commit 06e2db9

File tree

10 files changed

+153
-3
lines changed

10 files changed

+153
-3
lines changed

OAT.xml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,16 @@
5050
desc="hvigor plugin for autolinking, code generation, etc." />
5151
<filteritem type="filename" name="*.jpg|*.png|*.ttf" desc="desc files" />
5252
</filefilter>
53+
<filefilter name="copyrightPolicyFilter" desc="">
54+
<filteritem
55+
type="filepath"
56+
name="packages/tester/harmony/react_native_openharmony/src/main/cpp/RNOHCorePackage/ComponentDescriptors/ModalHostViewComponentDescriptor.h"
57+
desc="Upstream code with modifications using the original license header" />
58+
<filteritem
59+
type="filepath"
60+
name="packages/tester/harmony/react_native_openharmony/src/main/cpp/RNOHCorePackage/ComponentDescriptors/ModalHostViewComponentDescriptorProvider.h"
61+
desc="Upstream code with modifications using the original license header" />
62+
</filefilter>
5363
</filefilterlist>
5464
</oatconfig>
5565
</configuration>

packages/tester/harmony/react_native_openharmony/src/main/cpp/RNOH/RNInstance.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include <react/renderer/animations/LayoutAnimationDriver.h>
2222
#include <react/renderer/scheduler/Scheduler.h>
2323

24+
#include "RNOH/DisplayMetricsManager.h"
2425
#include "RNOH/MutationsToNapiConverter.h"
2526
#include "RNOH/TouchTarget.h"
2627
#include "RNOH/TurboModule.h"
@@ -54,6 +55,7 @@ class Surface {
5455
using Weak = std::weak_ptr<Surface>;
5556

5657
virtual LayoutContext getLayoutContext() = 0;
58+
virtual DisplayMetrics getDisplayMetrics() = 0;
5759
virtual std::weak_ptr<UIInputEventHandler> getUIInputEventHandler() = 0;
5860
};
5961

packages/tester/harmony/react_native_openharmony/src/main/cpp/RNOH/RNInstanceCAPI.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ void RNInstanceCAPI::createSurface(
120120
m_componentInstanceRegistry,
121121
m_componentInstanceFactory,
122122
m_arkTSMessageHub,
123+
m_arkTSBridge,
123124
surfaceId,
124125
m_id,
125126
moduleName));

packages/tester/harmony/react_native_openharmony/src/main/cpp/RNOH/RNInstanceInternal.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ void RNInstanceInternal::start() {
9090
m_runtimeScheduler = m_reactInstance->getRuntimeScheduler();
9191
m_contextContainer->insert<std::weak_ptr<react::RuntimeScheduler>>(
9292
"RuntimeScheduler", m_runtimeScheduler);
93+
m_contextContainer->insert<std::weak_ptr<RNInstance>>(
94+
"RNOH::RNInstance", weak_from_this());
9395

9496
m_turboModuleProvider = createTurboModuleProvider();
9597
initializeScheduler(m_turboModuleProvider);

packages/tester/harmony/react_native_openharmony/src/main/cpp/RNOH/arkui/ArkUISurface.cpp

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,13 +85,15 @@ ArkUISurface::ArkUISurface(
8585
ComponentInstanceRegistry::Shared componentInstanceRegistry,
8686
ComponentInstanceFactory::Shared const& componentInstanceFactory,
8787
ArkTSMessageHub::Shared arkTSMessageHub,
88+
DisplayMetricsManager::Shared displayMetricsManager,
8889
SurfaceId surfaceId,
8990
int rnInstanceId,
9091
std::string const& appKey)
9192
: m_surfaceId(surfaceId),
9293
m_scheduler(std::move(scheduler)),
9394
m_componentInstanceRegistry(std::move(componentInstanceRegistry)),
94-
m_surfaceHandler(SurfaceHandler(appKey, surfaceId)) {
95+
m_surfaceHandler(SurfaceHandler(appKey, surfaceId)),
96+
m_displayMetricsManager(std::move(displayMetricsManager)) {
9597
m_scheduler->registerSurface(m_surfaceHandler);
9698
m_taskExecutor = taskExecutor;
9799
m_rootView = componentInstanceFactory->create(
@@ -283,6 +285,15 @@ Surface::LayoutContext ArkUISurface::getLayoutContext() {
283285
return Surface::LayoutContext::from(m_surfaceHandler.getLayoutContext());
284286
}
285287

288+
DisplayMetrics ArkUISurface::getDisplayMetrics() {
289+
DisplayMetrics result;
290+
291+
m_taskExecutor->runSyncTask(TaskThread::MAIN, [this, &result] {
292+
result = m_displayMetricsManager->getDisplayMetrics();
293+
});
294+
return result;
295+
}
296+
286297
std::weak_ptr<UIInputEventHandler> ArkUISurface::getUIInputEventHandler() {
287298
return m_touchEventHandler;
288299
};

packages/tester/harmony/react_native_openharmony/src/main/cpp/RNOH/arkui/ArkUISurface.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ class ArkUISurface : public Surface,
3636
ComponentInstanceRegistry::Shared componentInstanceRegistry,
3737
ComponentInstanceFactory::Shared const& componentInstanceFactory,
3838
ArkTSMessageHub::Shared arkTSMessageHub,
39+
DisplayMetricsManager::Shared displayMetricsManager,
3940
facebook::react::SurfaceId surfaceId,
4041
int rnInstanceId,
4142
std::string const& appKey);
@@ -86,6 +87,7 @@ class ArkUISurface : public Surface,
8687
void stop(std::function<void()> onStop);
8788
void setDisplayMode(facebook::react::DisplayMode displayMode);
8889
Surface::LayoutContext getLayoutContext() override;
90+
DisplayMetrics getDisplayMetrics() override;
8991
std::weak_ptr<UIInputEventHandler> getUIInputEventHandler() override;
9092

9193
private:
@@ -98,6 +100,7 @@ class ArkUISurface : public Surface,
98100
ThreadGuard m_threadGuard{};
99101
std::shared_ptr<UIInputEventHandler> m_touchEventHandler;
100102
TaskExecutor::Shared m_taskExecutor;
103+
DisplayMetricsManager::Shared m_displayMetricsManager;
101104
};
102105

103106
} // namespace rnoh
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/**
2+
* Copyright (c) 2025 Huawei Technologies Co., Ltd.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#include "ModalHostViewComponentDescriptor.h"
9+
#include <react/renderer/components/modal/ModalHostViewShadowNode.h>
10+
#include <react/renderer/core/ConcreteComponentDescriptor.h>
11+
#include "RNOH/RNInstance.h"
12+
13+
namespace rnoh {
14+
15+
facebook::react::State::Shared
16+
ModalHostViewComponentDescriptor::createInitialState(
17+
const facebook::react::Props::Shared& props,
18+
const facebook::react::ShadowNodeFamily::Shared& family) const {
19+
facebook::react::Size screenSize = {0, 0};
20+
auto instance = contextContainer_->at<std::weak_ptr<rnoh::RNInstance>>(
21+
"RNOH::RNInstance");
22+
23+
auto maybeSurface =
24+
instance.lock()->getSurfaceByRootTag(family->getSurfaceId());
25+
if (maybeSurface.has_value()) {
26+
auto surface = maybeSurface.value().lock();
27+
if (surface) {
28+
auto displayMetrics = surface->getDisplayMetrics();
29+
screenSize.height = displayMetrics.windowPhysicalPixels.height /
30+
displayMetrics.windowPhysicalPixels.scale;
31+
screenSize.width = displayMetrics.windowPhysicalPixels.width /
32+
displayMetrics.windowPhysicalPixels.scale;
33+
}
34+
}
35+
auto data =
36+
std::make_shared<const facebook::react::ModalHostViewState>(screenSize);
37+
38+
return std::make_shared<
39+
facebook::react::ModalHostViewShadowNode::ConcreteState>(data, family);
40+
}
41+
42+
} // namespace rnoh
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#pragma once
9+
10+
#include <react/renderer/components/modal/ModalHostViewShadowNode.h>
11+
#include <react/renderer/core/ConcreteComponentDescriptor.h>
12+
#include "RNOH/RNInstance.h"
13+
14+
namespace rnoh {
15+
16+
/*
17+
* RNOH uses a custom ModalHostViewComponentDescriptor so that we can set the
18+
* initial state to the actual size of the screen and not the default {0,0}.
19+
* This fixes a bug in KeyboardAvoidingView which relies on the height value
20+
* from the initial onLayout.
21+
*/
22+
23+
class ModalHostViewComponentDescriptor final
24+
: public facebook::react::ConcreteComponentDescriptor<
25+
facebook::react::ModalHostViewShadowNode> {
26+
public:
27+
using ConcreteComponentDescriptor::ConcreteComponentDescriptor;
28+
29+
void adopt(facebook::react::ShadowNode& shadowNode) const override {
30+
auto& layoutableShadowNode =
31+
static_cast<facebook::react::YogaLayoutableShadowNode&>(shadowNode);
32+
auto& stateData =
33+
static_cast<
34+
const facebook::react::ModalHostViewShadowNode::ConcreteState&>(
35+
*shadowNode.getState())
36+
.getData();
37+
38+
layoutableShadowNode.setSize(facebook::react::Size{
39+
stateData.screenSize.width, stateData.screenSize.height});
40+
layoutableShadowNode.setPositionType(YGPositionTypeAbsolute);
41+
42+
ConcreteComponentDescriptor::adopt(shadowNode);
43+
}
44+
45+
facebook::react::State::Shared createInitialState(
46+
const facebook::react::Props::Shared& props,
47+
const facebook::react::ShadowNodeFamily::Shared& family) const override;
48+
};
49+
50+
} // namespace rnoh
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#pragma once
9+
10+
#include <react/renderer/componentregistry/ComponentDescriptorProvider.h>
11+
#include "ModalHostViewComponentDescriptor.h"
12+
13+
namespace rnoh {
14+
facebook::react::ComponentDescriptor::Unique
15+
createModalHostViewComponentDescriptor(
16+
const facebook::react::ComponentDescriptorParameters& parameters) {
17+
return std::make_unique<const rnoh::ModalHostViewComponentDescriptor>(
18+
parameters);
19+
}
20+
facebook::react::ComponentDescriptorProvider
21+
createModalHostViewComponentDescriptorProvider() {
22+
return facebook::react::ComponentDescriptorProvider{
23+
facebook::react::ModalHostViewShadowNode::Handle(),
24+
facebook::react::ModalHostViewShadowNode::Name(),
25+
nullptr,
26+
&createModalHostViewComponentDescriptor,
27+
};
28+
}
29+
} // namespace rnoh

packages/tester/harmony/react_native_openharmony/src/main/cpp/RNOHCorePackage/RNOHCorePackage.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include "RNOHCorePackage/ComponentBinders/TextComponentJSIBinder.h"
2727
#include "RNOHCorePackage/ComponentBinders/TextInputComponentJSIBinder.h"
2828
#include "RNOHCorePackage/ComponentBinders/ViewComponentJSIBinder.h"
29+
#include "RNOHCorePackage/ComponentDescriptors/ModalHostViewComponentDescriptorProvider.h"
2930
#include "RNOHCorePackage/ComponentInstances/ActivityIndicatorComponentInstance.h"
3031
#include "RNOHCorePackage/ComponentInstances/ImageComponentInstance.h"
3132
#include "RNOHCorePackage/ComponentInstances/ModalHostViewComponentInstance.h"
@@ -203,8 +204,7 @@ class RNOHCorePackage : public Package {
203204
facebook::react::ScrollViewComponentDescriptor>(),
204205
facebook::react::concreteComponentDescriptorProvider<
205206
facebook::react::PullToRefreshViewComponentDescriptor>(),
206-
facebook::react::concreteComponentDescriptorProvider<
207-
facebook::react::ModalHostViewComponentDescriptor>(),
207+
createModalHostViewComponentDescriptorProvider(),
208208
facebook::react::concreteComponentDescriptorProvider<
209209
facebook::react::SwitchComponentDescriptor>(),
210210
facebook::react::concreteComponentDescriptorProvider<

0 commit comments

Comments
 (0)