From 01d000c812a55f261b9ba1e566a5b199845f816f Mon Sep 17 00:00:00 2001 From: Janic Duplessis Date: Wed, 5 Apr 2023 07:09:33 -0700 Subject: [PATCH] Add fabric support for maintainVisibleContentPosition on Android (#35994) Summary: Implement a few missing bits for `maintainVisibleContentPosition` to work with fabric. The main thing needed is to add 2 fabric renderer listener methods to allow to hook into specific parts of the rendering process. We need some code to execute before view updates are executed and after view updates are executed. The current methods that are exposed do not work for this case. `willDispatchViewUpdates` is called from JS thread, and there doesn't seem to be a way to add UI blocks that will be executed at the right time like we do in paper. `didDispatchMountItems` is called for every frame which we don't want and will cause lots of overhead. After that we simply need to call the right methods in the new renderer listener methods. ## Changelog Pull Request resolved: https://github.com/facebook/react-native/pull/35994 Test Plan: Tested in RN tester maintainVisibleContentPosition example on Android with fabric enabled. Reviewed By: cipolleschi Differential Revision: D44131763 Pulled By: cortinico fbshipit-source-id: 32c0b5867d460537b18a70d472fd58052da6cf80 --- .../react/animated/NativeAnimatedModule.java | 10 +++++++ .../react/bridge/UIManagerListener.java | 27 +++++++++++++++++-- .../react/fabric/FabricUIManager.java | 14 ++++++++++ .../fabric/mounting/MountItemDispatcher.java | 9 +++++++ .../MaintainVisibleScrollPositionHelper.java | 19 +++++++++++++ 5 files changed, 77 insertions(+), 2 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.java index ce7520ee9bd942..76749aaff7f948 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.java @@ -295,6 +295,16 @@ public void didScheduleMountItems(UIManager uiManager) { mCurrentFrameNumber++; } + @Override + public void willMountItems(UIManager uiManager) { + // noop + } + + @Override + public void didMountItems(UIManager uiManager) { + // noop + } + // For FabricUIManager only @Override @UiThread diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/UIManagerListener.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/UIManagerListener.java index 984bcf15848ee8..505999b3d5af33 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/UIManagerListener.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/UIManagerListener.java @@ -12,10 +12,33 @@ public interface UIManagerListener { /** * Called right before view updates are dispatched at the end of a batch. This is useful if a * module needs to add UIBlocks to the queue before it is flushed. + * + *

This is called by Paper only. */ void willDispatchViewUpdates(UIManager uiManager); - /* Called right after view updates are dispatched for a frame. */ + /** + * Called on UIThread right before view updates are executed. + * + *

This is called by Fabric only. + */ + void willMountItems(UIManager uiManager); + /** + * Called on UIThread right after view updates are executed. + * + *

This is called by Fabric only. + */ + void didMountItems(UIManager uiManager); + /** + * Called on UIThread right after view updates are dispatched for a frame. Note that this will be + * called for every frame even if there are no updates. + * + *

This is called by Fabric only. + */ void didDispatchMountItems(UIManager uiManager); - /* Called right after scheduleMountItems is called in Fabric, after a new tree is committed. */ + /** + * Called right after scheduleMountItems is called in Fabric, after a new tree is committed. + * + *

This is called by Fabric only. + */ void didScheduleMountItems(UIManager uiManager); } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java index 0da8e55dd8d938..8e50e91a64d52e 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java @@ -1171,6 +1171,20 @@ public Map getPerformanceCounters() { } private class MountItemDispatchListener implements MountItemDispatcher.ItemDispatchListener { + @Override + public void willMountItems() { + for (UIManagerListener listener : mListeners) { + listener.willMountItems(FabricUIManager.this); + } + } + + @Override + public void didMountItems() { + for (UIManagerListener listener : mListeners) { + listener.didMountItems(FabricUIManager.this); + } + } + @Override public void didDispatchMountItems() { for (UIManagerListener listener : mListeners) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountItemDispatcher.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountItemDispatcher.java index 36abe8dd9f15cf..6861961c472640 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountItemDispatcher.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountItemDispatcher.java @@ -192,6 +192,8 @@ private boolean dispatchMountItems() { return false; } + mItemDispatchListener.willMountItems(); + // As an optimization, execute all ViewCommands first // This should be: // 1) Performant: ViewCommands are often a replacement for SetNativeProps, which we've always @@ -298,6 +300,9 @@ private boolean dispatchMountItems() { } mBatchedExecutionTime += SystemClock.uptimeMillis() - batchedExecutionStartTime; } + + mItemDispatchListener.didMountItems(); + Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); return true; @@ -414,6 +419,10 @@ private static void printMountItem(MountItem mountItem, String prefix) { } public interface ItemDispatchListener { + void willMountItems(); + + void didMountItems(); + void didDispatchMountItems(); } } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/MaintainVisibleScrollPositionHelper.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/MaintainVisibleScrollPositionHelper.java index 6ce4d8b23d2bbf..f76c432b59047c 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/MaintainVisibleScrollPositionHelper.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/MaintainVisibleScrollPositionHelper.java @@ -18,6 +18,7 @@ import com.facebook.react.bridge.UIManagerListener; import com.facebook.react.bridge.UiThreadUtil; import com.facebook.react.uimanager.UIManagerHelper; +import com.facebook.react.uimanager.common.UIManagerType; import com.facebook.react.uimanager.common.ViewUtil; import com.facebook.react.views.scroll.ReactScrollViewHelper.HasSmoothScroll; import com.facebook.react.views.view.ReactViewGroup; @@ -89,6 +90,14 @@ public void stop() { * been updated. */ public void updateScrollPosition() { + // On Fabric this will be called internally in `didMountItems`. + if (ViewUtil.getUIManagerType(mScrollView.getId()) == UIManagerType.FABRIC) { + return; + } + updateScrollPositionInternal(); + } + + private void updateScrollPositionInternal() { if (mConfig == null || mFirstVisibleView == null || mPrevFirstVisibleFrame == null) { return; } @@ -169,6 +178,16 @@ public void run() { }); } + @Override + public void willMountItems(UIManager uiManager) { + computeTargetView(); + } + + @Override + public void didMountItems(UIManager uiManager) { + updateScrollPositionInternal(); + } + @Override public void didDispatchMountItems(UIManager uiManager) { // noop