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

Fixed Android bug causing to remove wrong view after reordering by zIndex #10951

Closed
wants to merge 1 commit into from
Closed

Fixed Android bug causing to remove wrong view after reordering by zIndex #10951

wants to merge 1 commit into from

Conversation

asgvard
Copy link

@asgvard asgvard commented Nov 15, 2016

Commit 3d3b067 implemented zIndex support for Android. However after zIndex resorting of the views, the JS doesn't know about the new order. When dynamically removing the view from an array of views, JS sends the command to remove the view at certain index, which is not the same in Java anymore since the order of items changed.
This issue is also happens when adding view after reordering - it's added in the wrong position instead, but since it gets reordered again, it doesn't have visible effect, that's why it's only spotted when removing views.

Test Plan:
Related issue and the simple empty react-native 0.37 app where it was tested: #8968

Test app code:

import React, { Component } from 'react';
import {
  AppRegistry,
  StyleSheet,
  Text,
  View
} from 'react-native';

export default class zindex extends Component {
  constructor(props) {
    super(props);
    this.state = {
      showGreen: true
    };
  }
  render() {
    return (
      <View style={{flex: 1}}>
        <View style={[styles.item, {zIndex: 3, backgroundColor: 'red'}]}>
          <Text>zIndex: 3</Text>
        </View>
        {this.state.showGreen ?
          <View style={[styles.item, {zIndex: 2, backgroundColor: 'green', height: 90}]}>
            <Text>zIndex: 2</Text>
          </View> : null
        }
        <View style={[styles.item, {zIndex: 1, backgroundColor: 'blue', height: 120}]}>
          <Text>zIndex: 1</Text>
        </View>
        <View style={styles.button}>
          <Text onPress={() => this.setState({ showGreen: !this.state.showGreen })}>
            Toggle green
          </Text>
        </View>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  item: {
    position: 'absolute',
    left: 0,
    right: 0,
    top: 0,
    height: 60
  },
  button: {
    position: 'absolute',
    left: 0,
    right: 0,
    backgroundColor: 'gray',
    top: 150,
    height: 30
  }
});

AppRegistry.registerComponent('zindex', () => zindex);

Starting screen:
screen shot 2016-11-15 at 16 15 58

Before fix, when trying to remove green, it removes blue instead:
screen shot 2016-11-15 at 16 20 18

After fix it removed green correctly:
screen shot 2016-11-15 at 16 16 22

When clicking toggle again, it adds green back, the blue is still ordered correctly by zIndex:
screen shot 2016-11-15 at 16 16 44

Initially the order of operations in manageChildren in NativeViewHierarchyManager.java was:

  1. Loop by indicesToRemove, call viewManager.removeViewAt(viewToManage, indexToRemove). However if the view is animated and it's ID in the array of tagsToDelete, do nothing and delegate this job of removing item to the step 3).
  2. Loop by viewsToAdd, add the views.
  3. Loop by tagsToDelete, perform animations on the views that are using animations, then call viewManager.removeView(viewToManage, viewToDestroy) and dropView(viewToDestroy). But if the view is not animated, only dropView(viewToDestroy) is called because we assume that correct view was previously removed in step 1)

New order of operations:

  1. Loop by tagsToDelete, perform animations if necessary, then call viewManager.removeView(viewToManage, viewToDestroy) and dropView(viewToDestroy). If no animations, just call viewManager.removeView(viewToManage, viewToDestroy) and dropView(viewToDestroy) right away.
  2. Loop by viewsToAdd, add the views.

The intent is to perform clean-up by only looping by tagsToDelete first, instead of splitting the clean-up into separate steps of removing by index, then dropping by tagToDelete. And then when all the views are properly removed, we add the new views. Since indicesToRemove it's not reliable anymore and we don't use it for removeViewAt, we can get rid of the whole 1) step and replace it by 3) step.
Also by having the old operations order we having the case that the wrong view is removed in step 1), but then, correct view is dropped in step 3). In result we have inconsistency when removing one view but dropping another. It's described in more details in mentioned above related issue comments.

Because the diff doesn't look very clear because of replacing the blocks of code, here is what's actually changed:

  1. Removed the loop by indicesToRemove as it's not reliable anymore and causing wrong view to be removed.
  2. Moved loop by tagsToDelete before adding new views
  3. Added a call for viewManager.removeView(viewToManage, viewToDestroy) in else statement.

Commit 3d3b067 implemented zIndex support for Android. However after zIndex resorting of the views, the JS doesn't know about the new order. When dynamically removing the view from an array of views, JS sends the command to remove the view at certain index, which is not the same in Java since the order of items changed.

Test Plan:
Related issue and the simple empty react-native 0.37 app where it was tested:
#8968

Initially the order of operations in `manageChildren` in `NativeViewHierarchyManager.java` was:
1) Loop by `indicesToRemove`, call `viewManager.removeViewAt(viewToManage, indexToRemove)`. However if the view is delegated and it's ID in the array of `tagsToDelete`, do nothing and delegate this job of removing item to the step 3).
2) Loop by `viewsToAdd`, add the views.
3) Loop by `tagsToDelete`, perform animations on the views that are using animations, then call `viewManager.removeView(viewToManage, viewToDestroy)` and `dropView(viewToDestroy)`. But if the view is not animated, only `dropView(viewToDestroy)` is called because we assume that correct view was previously removed in step 1)

New order of operations:
1) Loop by `tagsToDelete`, perform animations if necessary, then call `viewManager.removeView(viewToManage, viewToDestroy)` and `dropView(viewToDestroy)`. If no animations, just call `viewManager.removeView(viewToManage, viewToDestroy)` and `dropView(viewToDestroy)` right away.
2) Loop by `viewsToAdd`, add the views.

The intent is to perform clean-up by only looping by `tagsToDelete` first, instead of splitting the clean-up into separate steps of removing by index, then dropping by tagToDelete. And then when all the views are properly removed, we add the new views. Since `indicesToRemove` it's not reliable anymore and we don't use it for `removeViewAt`, we can get rid of the whole 1) step and replace it by 3) step.
Also by having the old operations order we having the case that the *wrong* view is removed in step 1), but then, *correct* view is dropped in step 3). In result we have inconsistency when removing one view but dropping another. It's described in more details in mentioned above related issue comments.

Because the diff doesn't look very clear because of replacing the blocks of code, here is what's actually changed:
1. Removed the loop by `indicesToRemove` as it's not reliable anymore and causing wrong view to be removed.
2. Moved loop by `tagsToDelete` before adding new views
3. Added a call for `viewManager.removeView(viewToManage, viewToDestroy)` in `else` statement.
@facebook-github-bot
Copy link
Contributor

By analyzing the blame information on this pull request, we identified @janicduplessis and @dmmiller to be potential reviewers.

@facebook-github-bot facebook-github-bot added GH Review: review-needed CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. labels Nov 15, 2016
@asgvard
Copy link
Author

asgvard commented Nov 15, 2016

Hi,

Ok, my first PR not the perfect one :) Will double-check all the CI tests locally and come with another solution.

@asgvard asgvard closed this Nov 15, 2016
@asgvard asgvard deleted the bugfixes/android-z-index branch November 15, 2016 17:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants