diff --git a/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java b/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java index 843bc6acc2dd07..44dbc60da5b8c6 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java +++ b/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java @@ -115,4 +115,10 @@ public class ReactFeatureFlags { * state in Fabric SurfaceMountingManager. */ public static boolean reduceDeleteCreateMutationLayoutAnimation = true; + + /** + * Allow fix to drop delete...create mutations which could cause missing view state in Fabric + * SurfaceMountingManager. + */ + public static boolean reduceDeleteCreateMutation = false; } diff --git a/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.cpp b/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.cpp index 707ea46e50debb..b1097967515bfd 100644 --- a/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.cpp +++ b/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.cpp @@ -40,7 +40,9 @@ FabricMountingManager::FabricMountingManager( std::shared_ptr &config, global_ref &javaUIManager) : javaUIManager_(javaUIManager), - useOverflowInset_(getFeatureFlagValue("useOverflowInset")) { + useOverflowInset_(getFeatureFlagValue("useOverflowInset")), + reduceDeleteCreateMutation_( + getFeatureFlagValue("reduceDeleteCreateMutation")) { CoreFeatures::enableMapBuffer = getFeatureFlagValue("useMapBufferProps"); } @@ -314,9 +316,33 @@ void FabricMountingManager::executeMount( bool isVirtual = mutation.mutatedViewIsVirtual(); switch (mutationType) { case ShadowViewMutation::Create: { - bool allocationCheck = + bool shouldCreateView = !allocatedViewTags.contains(newChildShadowView.tag); - bool shouldCreateView = allocationCheck; + if (reduceDeleteCreateMutation_) { + // Detect DELETE...CREATE situation on the same node and do NOT push + // back to the mount items. This is an edge case that may happen + // when for example animation runs while commit happened, and we + // want to filter them out here to capture all possible sources of + // such mutations. The re-ordering logic here assumes no + // DELETE...CREATE in the mutations, as we will re-order mutations + // and batch all DELETE instructions in the end. + auto it = std::remove_if( + cppDeleteMountItems.begin(), + cppDeleteMountItems.end(), + [&](auto &deletedMountItem) -> bool { + return deletedMountItem.oldChildShadowView.tag == + newChildShadowView.tag; + }); + bool hasDeletedViewsWithSameTag = it != cppDeleteMountItems.end(); + cppDeleteMountItems.erase(it, cppDeleteMountItems.end()); + + if (hasDeletedViewsWithSameTag) { + shouldCreateView = false; + LOG(ERROR) + << "XIN: Detect DELETE...CREATE on the same tag from mutations in the same batch. The DELETE and CREATE mutations are removed before sending to the native platforms"; + } + } + if (shouldCreateView) { cppCommonMountItems.push_back( CppMountItem::CreateMountItem(newChildShadowView)); diff --git a/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.h b/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.h index 57e9eff06a6244..37440bf50198b0 100644 --- a/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.h +++ b/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.h @@ -70,6 +70,7 @@ class FabricMountingManager final { std::recursive_mutex allocatedViewsMutex_; bool const useOverflowInset_{false}; + bool const reduceDeleteCreateMutation_{false}; jni::local_ref getProps( ShadowView const &oldShadowView,