Skip to content

Commit 44345e5

Browse files
committed
Maker PoC of commit hook to detect orientation change
1 parent 629df83 commit 44345e5

File tree

11 files changed

+208
-9
lines changed

11 files changed

+208
-9
lines changed

FabricExample/android/settings.gradle

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,12 @@ extensions.configure(com.facebook.react.ReactSettingsExtension) { ex -> ex.autol
44
rootProject.name = 'FabricExample'
55
include ':app'
66
includeBuild('../node_modules/@react-native/gradle-plugin')
7+
8+
includeBuild('../node_modules/react-native') {
9+
dependencySubstitution {
10+
substitute(module("com.facebook.react:react-android")).using(project(":packages:react-native:ReactAndroid"))
11+
substitute(module("com.facebook.react:react-native")).using(project(":packages:react-native:ReactAndroid"))
12+
substitute(module("com.facebook.react:hermes-android")).using(project(":packages:react-native:ReactAndroid:hermes-engine"))
13+
substitute(module("com.facebook.react:hermes-engine")).using(project(":packages:react-native:ReactAndroid:hermes-engine"))
14+
}
15+
}

FabricExample/ios/Podfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3006,7 +3006,7 @@ SPEC CHECKSUMS:
30063006
ReactCommon: 9e5f2e80ecaeb6c70f21ae0db29caaff75dabe47
30073007
RNGestureHandler: f1dd7f92a0faa2868a919ab53bb9d66eb4ebfcf5
30083008
RNReanimated: aadd939e2bb8984700467ca876b422b0a07b75cb
3009-
RNScreens: 0bbf16c074ae6bb1058a7bf2d1ae017f4306797c
3009+
RNScreens: aacce18b7172667385e4e60c013306c18bcdce96
30103010
RNWorklets: 564218881b4c63ab941bd5b3be37ae585a30a1c6
30113011
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
30123012
Yoga: e80c5fabbc3e26311152fa20404cdfa14f16a11f

apps/App.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import React from 'react';
22
import { enableFreeze } from 'react-native-screens';
33
import Example from './Example';
4-
// import * as Test from './src/tests';
4+
import * as Test from './src/tests';
55

66
enableFreeze(true);
77

88
export default function App() {
9-
return <Example />;
10-
// return <Test.TestBottomTabs />;
9+
// return <Example />;
10+
return <Test.Test3295 />;
1111
}

apps/src/tests/Test3212/ScrollViewTemplate.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export function ScrollViewTemplate() {
66
return (
77
<ScrollView>
88
<Text style={{ fontSize: 21 }}>
9-
{Array.from({ length: 1000 }).map(_ => emoji[Math.floor(Math.random() * emoji.length)])}
9+
{Array.from({ length: 50000 }).map(_ => emoji[Math.floor(Math.random() * emoji.length)])}
1010
</Text>
1111
</ScrollView>
1212
);

apps/src/tests/Test3295.tsx

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import React from "react";
2+
import { NavigationContainer } from "@react-navigation/native";
3+
import { createNativeStackNavigator } from "@react-navigation/native-stack";
4+
import { Dimensions, ScrollView, Text, View } from "react-native";
5+
6+
function ScrollViewTemplate() {
7+
const emoji = ['😎', '🍏', '👀', '🤖', '👾', '👨‍💻'];
8+
console.log(`ScrollViewRender screen dimensions / width ${Dimensions.get('screen').width} / height ${Dimensions.get('screen').height}`);
9+
console.log(`ScrollViewRender window dimensions / width ${Dimensions.get('window').width} / height ${Dimensions.get('window').height}`);
10+
11+
return (
12+
<ScrollView>
13+
<Text style={{ fontSize: 21 }}>
14+
{Array.from({ length: 50000 }).map(_ => emoji[Math.floor(Math.random() * emoji.length)])}
15+
</Text>
16+
</ScrollView>
17+
);
18+
}
19+
20+
function Foo() {
21+
return (
22+
<View style={{ backgroundColor: 'red', flex: 1 }}></View>
23+
)
24+
}
25+
26+
export default function() {
27+
const Stack = createNativeStackNavigator();
28+
29+
return (
30+
<NavigationContainer>
31+
<Stack.Navigator screenOptions={{
32+
autoHideHomeIndicator: true,
33+
}}>
34+
<Stack.Screen name="Test" component={ScrollViewTemplate}/>
35+
</Stack.Navigator>
36+
</NavigationContainer>
37+
);
38+
}

apps/src/tests/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ export { default as Test3212 } from './Test3212';
153153
export { default as Test3236 } from './Test3236';
154154
export { default as Test3239 } from './Test3239';
155155
export { default as Test3265 } from './Test3265';
156+
export { default as Test3295 } from './Test3295';
156157
export { default as TestScreenAnimation } from './TestScreenAnimation';
157158
export { default as TestScreenAnimationV5 } from './TestScreenAnimationV5';
158159
export { default as TestHeader } from './TestHeader';

common/cpp/react/renderer/components/rnscreens/RNSScreenComponentDescriptor.h

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,13 @@
66
#include <react/debug/react_native_assert.h>
77
#include <react/renderer/components/rnscreens/Props.h>
88
#include <react/renderer/components/rnscreens/utils/RectUtil.h>
9+
#include <react/renderer/components/root/RootShadowNode.h>
910
#include <react/renderer/core/ConcreteComponentDescriptor.h>
11+
#include <react/renderer/uimanager/UIManager.h>
12+
#include <react/renderer/uimanager/UIManagerCommitHook.h>
13+
#include <memory>
1014
#include "RNSScreenShadowNode.h"
15+
#include "RNSScreenShadowNodeCommitHook.h"
1116

1217
namespace facebook {
1318
namespace react {
@@ -16,9 +21,29 @@ using namespace rnscreens;
1621

1722
class RNSScreenComponentDescriptor final
1823
: public ConcreteComponentDescriptor<RNSScreenShadowNode> {
24+
private:
25+
#ifdef ANDROID
26+
/*
27+
* A commit hook that triggers on `shadowTreeWillCommit` event,
28+
* and can read the properties of RootShadowNodes for determining screen
29+
* orientation.
30+
*/
31+
mutable std::shared_ptr<RNSScreenShadowNodeCommitHook> commitHook_;
32+
/*
33+
* The following flag is expected to be set by RNSScreenShadowNodeCommitHook,
34+
* when it detects that screen orientation has changed
35+
* by comparing width to height of old and new ShadowNode revision.
36+
*/
37+
mutable bool orientationDidChange_ = false;
38+
#endif // Android specific
39+
1940
public:
2041
using ConcreteComponentDescriptor::ConcreteComponentDescriptor;
2142

43+
void setOrientationDidChange() {
44+
orientationDidChange_ = true;
45+
}
46+
2247
void adopt(ShadowNode &shadowNode) const override {
2348
react_native_assert(dynamic_cast<RNSScreenShadowNode *>(&shadowNode));
2449
auto &screenShadowNode = static_cast<RNSScreenShadowNode &>(shadowNode);
@@ -34,7 +59,22 @@ class RNSScreenComponentDescriptor final
3459
auto stateData = state->getData();
3560

3661
#ifdef ANDROID
37-
if (stateData.frameSize.width != 0 && stateData.frameSize.height != 0) {
62+
if (!commitHook_) {
63+
// The hook couldn't be attached in constructor because UIManager was
64+
// missing from ContextContainer. Instead, we do it here, on the first
65+
// call to the function. We don't anticipate any orientation changes that
66+
// we need to respond to prior to this.
67+
commitHook_ = std::make_shared<RNSScreenShadowNodeCommitHook>(
68+
contextContainer_, const_cast<RNSScreenComponentDescriptor *>(this));
69+
}
70+
71+
if (orientationDidChange_) {
72+
orientationDidChange_ = false;
73+
screenShadowNode.getStateDataMutable().frameSize = {0, 0};
74+
screenShadowNode.getStateDataMutable().contentOffset = {0, 0};
75+
layoutableShadowNode.setSize({YGUndefined, YGUndefined});
76+
} else if (
77+
stateData.frameSize.width != 0 && stateData.frameSize.height != 0) {
3878
// When we receive dimensions from JVM side we can remove padding used for
3979
// correction, and we can stop applying height and offset corrections for
4080
// the frame.

common/cpp/react/renderer/components/rnscreens/RNSScreenShadowNode.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,11 @@ class JSI_EXPORT RNSScreenShadowNode final : public ConcreteViewShadowNode<
3838

3939
FrameCorrectionModes &getFrameCorrectionModes();
4040

41+
StateData &getStateDataMutable();
42+
4143
private:
4244
#ifdef ANDROID
4345
void applyFrameCorrections();
44-
45-
StateData &getStateDataMutable();
4646
#endif // ANDROID
4747
};
4848

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
#include "RNSScreenShadowNodeCommitHook.h"
2+
#include <react/fabric/FabricUIManagerBinding.h>
3+
#include <react/fabric/JFabricUIManager.h>
4+
#include <react/renderer/scheduler/Scheduler.h>
5+
#include "RNSScreenComponentDescriptor.h"
6+
7+
namespace facebook {
8+
namespace react {
9+
10+
RNSScreenShadowNodeCommitHook::RNSScreenShadowNodeCommitHook(
11+
std::shared_ptr<const ContextContainer> contextContainer,
12+
RNSScreenComponentDescriptor *screenComponentDescriptor)
13+
: contextContainer_(contextContainer),
14+
screenComponentDescriptor_(screenComponentDescriptor) {
15+
if (contextContainer_) {
16+
auto fabricUIManager = contextContainer_->at<jni::alias_ref<
17+
jni::detail::JTypeFor<JFabricUIManager, jni::JObject, void>::_javaobject
18+
*>>("FabricUIManager");
19+
auto uiManager =
20+
fabricUIManager->getBinding()->getScheduler()->getUIManager();
21+
uiManager->registerCommitHook(*this);
22+
}
23+
}
24+
25+
RNSScreenShadowNodeCommitHook::RNSScreenShadowNodeCommitHook(
26+
const RNSScreenShadowNodeCommitHook &other) {
27+
contextContainer_ = other.contextContainer_;
28+
screenComponentDescriptor_ = other.screenComponentDescriptor_;
29+
}
30+
31+
RNSScreenShadowNodeCommitHook::~RNSScreenShadowNodeCommitHook() noexcept {
32+
if (contextContainer_) {
33+
auto fabricUIManager = contextContainer_->at<jni::alias_ref<
34+
jni::detail::JTypeFor<JFabricUIManager, jni::JObject, void>::_javaobject
35+
*>>("FabricUIManager");
36+
auto uiManager =
37+
fabricUIManager->getBinding()->getScheduler()->getUIManager();
38+
uiManager->unregisterCommitHook(*this);
39+
}
40+
}
41+
42+
RootShadowNode::Unshared RNSScreenShadowNodeCommitHook::shadowTreeWillCommit(
43+
const facebook::react::ShadowTree &shadowTree,
44+
const RootShadowNode::Shared &oldRootShadowNode,
45+
const RootShadowNode::Unshared &newRootShadowNode,
46+
const ShadowTreeCommitOptions &) noexcept {
47+
__android_log_print(ANDROID_LOG_INFO, "LAYOUT", "shadowTreeWillCommit");
48+
49+
auto oldRootProps =
50+
std::static_pointer_cast<const RootProps>(oldRootShadowNode->getProps());
51+
auto newRootProps =
52+
std::static_pointer_cast<const RootProps>(newRootShadowNode->getProps());
53+
54+
const bool wasHorizontal = isHorizontal_(oldRootProps);
55+
const bool willBeHorizontal = isHorizontal_(newRootProps);
56+
57+
if ((wasHorizontal && !willBeHorizontal) ||
58+
(!wasHorizontal && willBeHorizontal)) {
59+
screenComponentDescriptor_->setOrientationDidChange();
60+
}
61+
62+
return std::static_pointer_cast<RootShadowNode>(
63+
newRootShadowNode->ShadowNode::clone(ShadowNodeFragment{}));
64+
}
65+
66+
} // namespace react
67+
} // namespace facebook
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#pragma once
2+
3+
#include <react/renderer/uimanager/UIManagerCommitHook.h>
4+
5+
namespace facebook {
6+
namespace react {
7+
8+
class RNSScreenComponentDescriptor;
9+
10+
class RNSScreenShadowNodeCommitHook : public UIManagerCommitHook {
11+
public:
12+
RNSScreenShadowNodeCommitHook(
13+
std::shared_ptr<const ContextContainer>,
14+
RNSScreenComponentDescriptor *);
15+
16+
RNSScreenShadowNodeCommitHook(const RNSScreenShadowNodeCommitHook &);
17+
18+
virtual ~RNSScreenShadowNodeCommitHook() noexcept override;
19+
20+
virtual void commitHookWasRegistered(const UIManager &) noexcept override {}
21+
22+
virtual void commitHookWasUnregistered(const UIManager &) noexcept override {}
23+
24+
virtual RootShadowNode::Unshared shadowTreeWillCommit(
25+
const ShadowTree &shadowTree,
26+
const RootShadowNode::Shared &oldRootShadowNode,
27+
const RootShadowNode::Unshared &newRootShadowNode,
28+
const ShadowTreeCommitOptions & /*commitOptions*/) noexcept override;
29+
30+
private:
31+
std::shared_ptr<const ContextContainer> contextContainer_;
32+
RNSScreenComponentDescriptor *screenComponentDescriptor_;
33+
34+
inline bool isHorizontal_(std::weak_ptr<const RootProps> props) {
35+
const float width = props.lock().get()->layoutConstraints.maximumSize.width;
36+
const float height =
37+
props.lock().get()->layoutConstraints.maximumSize.height;
38+
39+
return width > height;
40+
};
41+
};
42+
43+
} // namespace react
44+
} // namespace facebook

0 commit comments

Comments
 (0)