-
-
Notifications
You must be signed in to change notification settings - Fork 597
Fix: layout shift on orientation change #3295
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
3209320 to
44345e5
Compare
8eb4111 to
60ed155
Compare
kkafar
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good job! I've left few remarks & questions. Please answer them.
common/cpp/react/renderer/components/rnscreens/RNSScreenComponentDescriptor.h
Outdated
Show resolved
Hide resolved
| void adopt(ShadowNode &shadowNode) const override { | ||
| react_native_assert(dynamic_cast<RNSScreenShadowNode *>(&shadowNode)); | ||
| auto &screenShadowNode = static_cast<RNSScreenShadowNode &>(shadowNode); | ||
| auto &screenShadowNode = dynamic_cast<RNSScreenShadowNode &>(shadowNode); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why change here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My bad, changed it back to static_cast. After consulting, I also changed the cast style in RNSScreenProps
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
common/cpp/react/renderer/components/rnscreens/RNSScreenShadowNodeCommitHook.cpp
Outdated
Show resolved
Hide resolved
common/cpp/react/renderer/components/rnscreens/RNSScreenShadowNodeCommitHook.cpp
Outdated
Show resolved
Hide resolved
| if (contextContainer_) { | ||
| auto fabricUIManager = | ||
| contextContainer_ | ||
| ->at<jni::alias_ref<facebook::react::JFabricUIManager::javaobject>>( | ||
| "FabricUIManager"); | ||
| fabricUIManager->getBinding() | ||
| ->getScheduler() | ||
| ->getUIManager() | ||
| ->unregisterCommitHook(*this); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We never cleanup reference to the contextContainer. Shall we do it here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we? doesn't the smart pointer handle this on its own?
common/cpp/react/renderer/components/rnscreens/RNSScreenShadowNodeCommitHook.cpp
Outdated
Show resolved
Hide resolved
common/cpp/react/renderer/components/rnscreens/RNSScreenShadowNodeCommitHook.cpp
Outdated
Show resolved
Hide resolved
| const ShadowTreeCommitOptions & /*commitOptions*/) noexcept override; | ||
|
|
||
| private: | ||
| std::shared_ptr<const ContextContainer> contextContainer_; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the decision of retaining the context container here intentional?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the commit hook needs to unregister in the destructor, I need the ref to the container
Fixes #2933
Description
This PR fixes the bug on android that results in Screen orientation being calculated incorrectly for some time after the physical device orientation has changed. This bug seems to happen due to asynchronous nature of Screen state update emitted to react from native layer. The state contains a size that is set on the Screen frame.
The bug is more visible on slower devices, because with longer processing of consecutive commits, the delay is longer.
It can be noticed on a trivial application (some text + different background color for screen and content view) with some heavy computations artificially added to JS.
More detailed description:
In
RNSScreenComponentDescriptor, on Android specifically, we callsetSize()on the Screen view to hardcode its dimensions. This is needed to account for screen header size, of which React has no idea. It is however the core reason for the bug. The size which we set is outdated. Only after the asynchronous state update from native side is processed, it is corrected. This can take some time. If we comment the code for handling the header size, this problem disappears. But we cannot.Since the code is staying here, we need to work with it. We agreed on the special value of frameSize = { 0, 0 }, for which we set YogaNode size to Undefined. This forces Yoga to recalculate the layout. It can do so because the parent view is correctly sized at this point (no calls to
setSize()with outdated values). We cannot do this manually, because the descriptor doesn't have access to parent nodes. Using the commit hook, we detect the orientation change (it can compare old and new RootShadowNode, which have the correct dimensions) and set the frameSize for the ShadowNode which is passed to the descriptor afterwards.Another problem is that the hook detects the change only once, for one instance of the shadow node. There could be more instances processed before the state is corrected. Luckily, React uses the same state instance for each shadow node instance between the updates. We take advantage of that, once reset to 0, the frameSize stays that way until some other updates changes the instance. Since we only store the screen offset and frameSize in the state, this works good enough. But it's still pretty hacky.
Changes
Description above. A new feature flag is added. When set, we register a new commit hook that checks if screen orientation changed & sets frameSize to 0. It is detected in Screen descriptor and Yoga is forced to recalculate the layout.
Before / After
android-layout-bad.mov
android-layout-better.mov
Testing
Use Test2933. You can take a look at the "checkerboard" test from react-native-reanimated.