Files
react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricReconciler.java
David Vacca 5347ecfd29 Fix removal of ReactShadowNode
Reviewed By: achen1

Differential Revision: D7323294

fbshipit-source-id: 411aa1bcd93cc4f9df78f64ceb0c0d8c127bc3b0
2018-03-19 18:31:36 -07:00

126 lines
5.5 KiB
Java

/**
* Copyright (c) 2014-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.fabric;
import android.util.Log;
import com.facebook.react.common.ArrayUtils;
import com.facebook.react.uimanager.ReactShadowNode;
import com.facebook.react.uimanager.UIViewOperationQueue;
import com.facebook.react.uimanager.ViewAtIndex;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import javax.annotation.Nullable;
public class FabricReconciler {
private static final String TAG = FabricReconciler.class.getSimpleName();
private static final boolean DEBUG = true;
private UIViewOperationQueue uiViewOperationQueue;
public FabricReconciler(UIViewOperationQueue uiViewOperationQueue) {
this.uiViewOperationQueue = uiViewOperationQueue;
}
public void manageChildren(ReactShadowNode previousRootShadowNode, ReactShadowNode newRootShadowNode) {
List<ReactShadowNode> prevList =
previousRootShadowNode == null ? null : previousRootShadowNode.getChildrenList();
manageChildren(newRootShadowNode, prevList, newRootShadowNode.getChildrenList());
}
private void manageChildren(
ReactShadowNode parent,
@Nullable List<ReactShadowNode> prevList,
@Nullable List<ReactShadowNode> newList) {
prevList = prevList == null ? Collections.<ReactShadowNode>emptyList() : prevList;
newList = newList == null ? Collections.<ReactShadowNode>emptyList() : newList;
// Iterate through each child list and compare each previous and next child. Same nodes
// implies no change is needed. If the nodes are different but are referencing the same view,
// the view needs to be updated with new props and children. Otherwise, there has been
// a change in the children positions.
int sameReactTagIndex = 0;
for (; sameReactTagIndex < Math.min(prevList.size(), newList.size()); sameReactTagIndex++) {
ReactShadowNode prevNode = prevList.get(sameReactTagIndex);
ReactShadowNode newNode = newList.get(sameReactTagIndex);
if (prevNode == newNode) {
continue;
}
if (prevNode.getReactTag() != newNode.getReactTag()) {
break;
}
if (newNode.getNewProps() != null) {
uiViewOperationQueue.enqueueUpdateProperties(
newNode.getReactTag(), newNode.getViewClass(), newNode.getNewProps());
}
manageChildren(prevNode, prevNode.getChildrenList(), newNode.getChildrenList());
prevNode.setOriginalReactShadowNode(newNode);
}
int firstRemovedOrAddedViewIndex = sameReactTagIndex;
// Every ReactShadowNode on the newList that is on the right side of
// firstRemovedOrAddedViewIndex is defined as an added view.
// It is more efficient to reorder removing and adding all the views in the right order, instead
// of calculating the minimum amount of reorder operations.
Set<Integer> addedTags = new HashSet<>();
ViewAtIndex[] viewsToAdd = new ViewAtIndex[newList.size() - firstRemovedOrAddedViewIndex];
int viewsToAddIndex = 0;
for (int k = firstRemovedOrAddedViewIndex; k < newList.size(); k++) {
ReactShadowNode newNode = newList.get(k);
if (newNode.getNewProps() != null) {
uiViewOperationQueue.enqueueUpdateProperties(
newNode.getReactTag(), newNode.getViewClass(), newNode.getNewProps());
}
viewsToAdd[viewsToAddIndex++] = new ViewAtIndex(newNode.getReactTag(), k);
List previousChildrenList = newNode.getOriginalReactShadowNode() == null ? null : newNode.getOriginalReactShadowNode().getChildrenList();
manageChildren(newNode, previousChildrenList, newNode.getChildrenList());
newNode.setOriginalReactShadowNode(newNode);
addedTags.add(newNode.getReactTag());
}
// Every ReactShadowNode on the prevList that is on the right side of
// firstRemovedOrAddedViewIndex is defined as a removed view.
// It is more efficient to reorder removing and adding all the views in the right order, instead
// of calculating the minimum amount of reorder operations.
// If a View is not re-ordered, then the ReactTag is deleted (ReactShadowNode and native View
// are released from memory)
List<Integer> tagsToDelete = new LinkedList<>();
int[] indicesToRemove = new int[prevList.size() - firstRemovedOrAddedViewIndex];
int indicesToRemoveIndex = 0;
for (int j = firstRemovedOrAddedViewIndex; j < prevList.size(); j++) {
ReactShadowNode nodeToRemove = prevList.get(j);
indicesToRemove[indicesToRemoveIndex++] = j;
if (!addedTags.contains(nodeToRemove.getReactTag())) {
tagsToDelete.add(nodeToRemove.getReactTag());
// TODO: T26729293 since we are not cloning ReactShadowNode's we need to "manually" remove
// from the ReactShadowTree when one of the nodes is deleted in JS.
nodeToRemove.getParent().removeChildAt(j);
}
}
int[] tagsToDeleteArray = ArrayUtils.copyListToArray(tagsToDelete);
if (DEBUG) {
Log.d(
TAG,
"manageChildren.enqueueManageChildren parent: " + parent.getReactTag() +
"\n\tIndices2Remove: " + Arrays.toString(indicesToRemove) +
"\n\tViews2Add: " + Arrays.toString(viewsToAdd) +
"\n\tTags2Delete: " + Arrays.toString(tagsToDeleteArray));
}
uiViewOperationQueue.enqueueManageChildren(
parent.getReactTag(), indicesToRemove, viewsToAdd, tagsToDeleteArray);
}
}