Skip to content
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

[Android] Redundant onLayout event #7202

Closed
4 tasks done
hayeah opened this issue Apr 24, 2016 · 9 comments
Closed
4 tasks done

[Android] Redundant onLayout event #7202

hayeah opened this issue Apr 24, 2016 · 9 comments
Labels
Resolution: Locked This issue was locked by the bot.

Comments

@hayeah
Copy link
Contributor

hayeah commented Apr 24, 2016

Sample App: https://rnplay.org/apps/L_-nNQ

I am using a RootView component to detect device rotation, but when animating an absolutely positioned child View in the class hierarchy, the RootView is getting redundant layout events.

<RootView onLayout={onDeviceRotation}>
   <Animated.View>
   </Animated.View>
</RootView>

On Android, onDeviceRotation gets a layout event for each frame (when the child view changes). On iOS, onDeviceRotation is called only on initial layout.

Digging into the source code, I found that:

  • On iOS, if a view is dirtied but if its frame doesn't change, then onLayout doesn't happen.
  • On Android, if a view is dirtied but if its frame doesn't change, onLayout DOES happen.

sample_app___react_native_playground

The relevant code is below.

iOS Side

RCTUIManager dispatches onLayout event for views that received new frames.

See:

for (RCTShadowView *shadowView in viewsWithNewFrames) {
RCTViewManager *manager = [_componentDataByName[shadowView.viewName] manager];
RCTViewManagerUIBlock block = [manager uiBlockToAmendWithShadowView:shadowView];
if (block) {
updateBlocks[shadowView.reactTag] = block;
}
if (shadowView.onLayout) {
CGRect frame = shadowView.frame;
shadowView.onLayout(@{
@"layout": @{
@"x": @(frame.origin.x),
@"y": @(frame.origin.y),
@"width": @(frame.size.width),
@"height": @(frame.size.height),
},
});
}

  for (RCTShadowView *shadowView in viewsWithNewFrames) {
    ...
    if (shadowView.onLayout) {
      CGRect frame = shadowView.frame;
      shadowView.onLayout(@{
        @"layout": @{
          @"x": @(frame.origin.x),
          @"y": @(frame.origin.y),
          @"width": @(frame.size.width),
          @"height": @(frame.size.height),
        },
      });
    }

RCTShadowNodes checks if frame changed, it not, don't add the view to viewsWithNewFrames, thus preventing onLayout event to get dispatched.

See:

if (!CGRectEqualToRect(frame, _frame)) {
_frame = frame;
[viewsWithNewFrame addObject:self];
}

if (!CGRectEqualToRect(frame, _frame)) {
  _frame = frame;
  [viewsWithNewFrame addObject:self];
}

Android Side

Android always dispatch an event if the layout is dirtied. In our case, the child view dirties the parent. We can do something similar as iOS, by checking if the frame changed before dispatching an event.

See:

cssNode.dispatchUpdates(
absoluteX,
absoluteY,
mOperationsQueue,
mNativeViewHierarchyOptimizer);
// notify JS about layout event if requested
if (cssNode.shouldNotifyOnLayout()) {
eventDispatcher.dispatchEvent(
OnLayoutEvent.obtain(
tag,
cssNode.getScreenX(),
cssNode.getScreenY(),
cssNode.getScreenWidth(),
cssNode.getScreenHeight()));
}

cssNode.dispatchUpdates(
          absoluteX,
          absoluteY,
          mOperationsQueue,
          mNativeViewHierarchyOptimizer);

      // notify JS about layout event if requested
      if (cssNode.shouldNotifyOnLayout()) {
        eventDispatcher.dispatchEvent(
            OnLayoutEvent.obtain(
                tag,
                cssNode.getScreenX(),
                cssNode.getScreenY(),
                cssNode.getScreenWidth(),
                cssNode.getScreenHeight()));
      }

Specifically, we can change dispatchUpdates to return false if frame didn't change, and avoid dispatching the onLayout event.


  • Provide a minimal code snippet / rnplay example that reproduces the bug.
  • [x Provide screenshots where appropriate
  • What's the version of React Native you're using? 0.24.1
  • Does this occur on iOS, Android or both? Inconsistency between Android and iOS.
  • Are you using Mac, Linux or Windows? Mac
@hayeah
Copy link
Contributor Author

hayeah commented Apr 27, 2016

fix #7250

@mkonicek
Copy link
Contributor

I asked on Twitter and there's a module to detect screen rotation properly, you might want to use that instead:
https://github.com/walmartreact/react-native-orientation-listener

Your PR #7250 might still make sense though (let's wait for others to comment).

@satya164
Copy link
Contributor

@mkonicek It wasn't very usable on Android last time I tried it though - https://github.com/walmartreact/react-native-orientation-listener/issues

@erikatg
Copy link

erikatg commented May 9, 2016

I ran into this as well while evaluating different methods of detecting orientation change. There's various problems (at least for android) with the orientation-plugins out there.

@thoblr
Copy link

thoblr commented May 26, 2016

@mkconicek This issue causes problem on other cases than just orientation changes. For example, I've experience performance issues related to redundant layout events when animating a parent to a view that has on layout. In the app I'm working on I use onLayout to setState and this is causing the Android version to re-render component tree during animation and in turn causing animation to lag considerably.

I've created a small reproducer to show the reduntant layout events when animating here: [https://rnplay.org/apps/DUpmUA]. The result is similar to @hayeah's example, I.E. animation does not trigger any layout event on iOS but on Android it triggers plenty of them.

For me the PR #7250 fixes the issue and makes Android behave like iOS. (Merged it into 0.26-stable)

@mkonicek
Copy link
Contributor

Hi there! This issue is being closed because it has been inactive for a while.

But don't worry, it will live on with ProductPains! Check out its new home: https://productpains.com/post/react-native/android-redundant-onlayout-event

Product Pains has been very useful in highlighting the top bugs and feature requests:
https://productpains.com/product/react-native?tab=top

Also, if this issue is a bug, please consider sending a pull request with a fix.

@mkonicek mkonicek reopened this Oct 31, 2016
facebook-github-bot pushed a commit that referenced this issue Oct 31, 2016
Summary:
Fixes [#7202 Android Redundant onLayout event](#7202)
Closes #7250

Differential Revision: D4104066

Pulled By: mkonicek

fbshipit-source-id: 383efdb4b4881aa7d7e508d61c9c01165bcf7bb6
@hayeah
Copy link
Contributor Author

hayeah commented Nov 8, 2016

I believe this issue is resolved and merged #7250

@hayeah hayeah closed this as completed Nov 8, 2016
DanielMSchmidt pushed a commit to DanielMSchmidt/react-native that referenced this issue Jan 4, 2017
Summary:
Fixes [facebook#7202 Android Redundant onLayout event](facebook#7202)
Closes facebook#7250

Differential Revision: D4104066

Pulled By: mkonicek

fbshipit-source-id: 383efdb4b4881aa7d7e508d61c9c01165bcf7bb6
@facebook facebook locked as resolved and limited conversation to collaborators Jul 19, 2018
@react-native-bot react-native-bot added the Resolution: Locked This issue was locked by the bot. label Jul 19, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Resolution: Locked This issue was locked by the bot.
Projects
None yet
Development

No branches or pull requests

8 participants
@thoblr @hayeah @mkonicek @satya164 @christopherdro @erikatg @react-native-bot and others