Skip to content

Commit 6a4d011

Browse files
cortinicofacebook-github-bot
authored andcommitted
Backport addUIBlock and prependUIBlock to Fabric for Android
Summary: The `.addUIBlock` and `.prependUIBlock` APIs on UiManagerModule are missing on Fabric. Here I'm re-implementing them to make migration to Fabric easier. Set of changes: - Moved `NativeViewHierarchyManager` to `NativeViewHierarchyManagerImpl` and extracted an interface - Moved `addUIBlock` and `prependUIBlock` to the shared `UIManager` interface - Added a `InteropUIBlockListener` class that takes care of executing the UI Blocks, implemented as a `UIManagerListener` Changelog: [Android] [Changed] - Changed the API of addUIBlock and prependUIBlock to implement it also in Fabric. Reviewed By: mdvacca Differential Revision: D53612514 fbshipit-source-id: 1cddbc391477318064f15c733a380983c3737373
1 parent cc2a73a commit 6a4d011

File tree

7 files changed

+364
-0
lines changed

7 files changed

+364
-0
lines changed

packages/react-native/ReactAndroid/api/ReactAndroid.api

+10
Original file line numberDiff line numberDiff line change
@@ -2520,6 +2520,7 @@ public class com/facebook/react/fabric/FabricUIManager : com/facebook/react/brid
25202520
public field mDevToolsReactPerfLogger Lcom/facebook/react/fabric/DevToolsReactPerfLogger;
25212521
public fun <init> (Lcom/facebook/react/bridge/ReactApplicationContext;Lcom/facebook/react/uimanager/ViewManagerRegistry;Lcom/facebook/react/uimanager/events/BatchEventDispatchedListener;)V
25222522
public fun addRootView (Landroid/view/View;Lcom/facebook/react/bridge/WritableMap;)I
2523+
public fun addUIBlock (Lcom/facebook/react/fabric/interop/UIBlock;)V
25232524
public fun addUIManagerEventListener (Lcom/facebook/react/bridge/UIManagerListener;)V
25242525
public fun attachRootView (Lcom/facebook/react/interfaces/fabric/SurfaceHandler;Landroid/view/View;)V
25252526
public fun clearJSResponder ()V
@@ -2541,6 +2542,7 @@ public class com/facebook/react/fabric/FabricUIManager : com/facebook/react/brid
25412542
public fun onHostPause ()V
25422543
public fun onHostResume ()V
25432544
public fun onRequestEventBeat ()V
2545+
public fun prependUIBlock (Lcom/facebook/react/fabric/interop/UIBlock;)V
25442546
public fun profileNextBatch ()V
25452547
public fun receiveEvent (IILjava/lang/String;Lcom/facebook/react/bridge/WritableMap;)V
25462548
public fun receiveEvent (IILjava/lang/String;ZLcom/facebook/react/bridge/WritableMap;I)V
@@ -2633,6 +2635,14 @@ public class com/facebook/react/fabric/events/FabricEventEmitter : com/facebook/
26332635
public fun receiveTouches (Ljava/lang/String;Lcom/facebook/react/bridge/WritableArray;Lcom/facebook/react/bridge/WritableArray;)V
26342636
}
26352637

2638+
public abstract interface class com/facebook/react/fabric/interop/UIBlock {
2639+
public abstract fun execute (Lcom/facebook/react/fabric/interop/UIBlockViewResolver;)V
2640+
}
2641+
2642+
public abstract interface class com/facebook/react/fabric/interop/UIBlockViewResolver {
2643+
public abstract fun resolveView (I)Landroid/view/View;
2644+
}
2645+
26362646
public abstract interface class com/facebook/react/fabric/mounting/LayoutMetricsConversions {
26372647
public static fun getMaxSize (I)F
26382648
public static fun getMinSize (I)F

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java

+37
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@
5656
import com.facebook.react.config.ReactFeatureFlags;
5757
import com.facebook.react.fabric.events.EventEmitterWrapper;
5858
import com.facebook.react.fabric.events.FabricEventEmitter;
59+
import com.facebook.react.fabric.internal.interop.InteropUIBlockListener;
60+
import com.facebook.react.fabric.interop.UIBlock;
5961
import com.facebook.react.fabric.mounting.MountItemDispatcher;
6062
import com.facebook.react.fabric.mounting.MountingManager;
6163
import com.facebook.react.fabric.mounting.SurfaceMountingManager;
@@ -209,6 +211,10 @@ public void executeItems(Queue<MountItem> items) {
209211
}
210212
};
211213

214+
// Interop UIManagerListener used to support addUIBlock and prependUIBlock.
215+
// It's initialized only when addUIBlock or prependUIBlock is called the first time.
216+
@Nullable private InteropUIBlockListener mInteropUIBlockListener;
217+
212218
public FabricUIManager(
213219
@NonNull ReactApplicationContext reactContext,
214220
@NonNull ViewManagerRegistry viewManagerRegistry,
@@ -446,6 +452,37 @@ public void invalidate() {
446452
}
447453
}
448454

455+
/**
456+
* Method added to Fabric for backward compatibility reasons, as users on Paper could call
457+
* [addUiBlock] and [prependUiBlock] on UIManagerModule.
458+
*/
459+
public void addUIBlock(UIBlock block) {
460+
if (ReactFeatureFlags.unstable_useFabricInterop) {
461+
InteropUIBlockListener listener = getInteropUIBlockListener();
462+
listener.addUIBlock(block);
463+
}
464+
}
465+
466+
/**
467+
* Method added to Fabric for backward compatibility reasons, as users on Paper could call
468+
* [addUiBlock] and [prependUiBlock] on UIManagerModule.
469+
*/
470+
public void prependUIBlock(UIBlock block) {
471+
if (ReactFeatureFlags.unstable_useFabricInterop) {
472+
InteropUIBlockListener listener = getInteropUIBlockListener();
473+
listener.prependUIBlock(block);
474+
}
475+
}
476+
477+
@NonNull
478+
private InteropUIBlockListener getInteropUIBlockListener() {
479+
if (mInteropUIBlockListener == null) {
480+
mInteropUIBlockListener = new InteropUIBlockListener();
481+
addUIManagerEventListener(mInteropUIBlockListener);
482+
}
483+
return mInteropUIBlockListener;
484+
}
485+
449486
@SuppressWarnings("unused")
450487
private NativeArray measureLines(
451488
ReadableMap attributedString, ReadableMap paragraphAttributes, float width, float height) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
@file:Suppress("DEPRECATION") // We want to test UIBlock and UIBlockViewResolver here.
9+
10+
package com.facebook.react.fabric.internal.interop
11+
12+
import com.facebook.react.bridge.UIManager
13+
import com.facebook.react.bridge.UIManagerListener
14+
import com.facebook.react.fabric.interop.UIBlock
15+
import com.facebook.react.fabric.interop.UIBlockViewResolver
16+
17+
/**
18+
* Interop class used to support invoking [addUIBlock] and [prependUIBlock] in Fabric.
19+
*
20+
* Users on the Old Architecture used to call those methods to execute arbitrary [UIBlock]s This
21+
* class effectively re-implements this logic by using a [UIManagerListener] and exposing the two
22+
* methods that the user intend to call.
23+
*/
24+
internal class InteropUIBlockListener : UIManagerListener {
25+
26+
internal val beforeUIBlocks: MutableList<UIBlock> = mutableListOf()
27+
internal val afterUIBlocks: MutableList<UIBlock> = mutableListOf()
28+
29+
@Synchronized
30+
fun prependUIBlock(block: UIBlock) {
31+
beforeUIBlocks.add(block)
32+
}
33+
34+
@Synchronized
35+
fun addUIBlock(block: UIBlock) {
36+
afterUIBlocks.add(block)
37+
}
38+
39+
override fun willMountItems(uiManager: UIManager) {
40+
beforeUIBlocks.forEach {
41+
if (uiManager is UIBlockViewResolver) {
42+
it.execute(uiManager)
43+
}
44+
}
45+
beforeUIBlocks.clear()
46+
}
47+
48+
override fun didMountItems(uiManager: UIManager) {
49+
afterUIBlocks.forEach {
50+
if (uiManager is UIBlockViewResolver) {
51+
it.execute(uiManager)
52+
}
53+
}
54+
afterUIBlocks.clear()
55+
}
56+
57+
override fun willDispatchViewUpdates(uiManager: UIManager) = Unit
58+
59+
override fun didDispatchMountItems(uiManager: UIManager) = Unit
60+
61+
override fun didScheduleMountItems(uiManager: UIManager) = Unit
62+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
@file:Suppress("DEPRECATION") // Deprecation is added for backward compatibility reasons
9+
10+
package com.facebook.react.fabric.interop
11+
12+
/**
13+
* Interop Interface added to support `addUiBlock` and `prependUIBlock` methods in Fabric.
14+
* Historically those methods were only available in `UIManagerModule` (Paper, the old renderer).
15+
* We're re-adding them to Fabric to make it easier to migrate.
16+
*
17+
* @deprecated When developing new libraries for Fabric you should instead use [UIManagerListener]
18+
* or View Commands to achieve a same results.
19+
*/
20+
@Deprecated("Use UIManagerListener or View Commands instead of addUIBlock and prependUIBlock.")
21+
public fun interface UIBlock {
22+
/**
23+
* A method that will allow you to call [UIBlockViewResolver.resolveView] to obtain a view
24+
* instance for a given tag.
25+
*/
26+
public fun execute(resolver: UIBlockViewResolver)
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
package com.facebook.react.fabric.interop
9+
10+
import android.view.View
11+
12+
/**
13+
* This interface is used as receiver parameter for [UIBlock].
14+
*
15+
* Users can invoke [resolveView] on this instance to get an Android [View] from the view tag.
16+
*
17+
* @deprecated When developing new libraries for Fabric you should instead use [UIManagerListener]
18+
* or View Commands to achieve a same results.
19+
*/
20+
@Deprecated("Use UIManagerListener or View Commands instead of addUIBlock and prependUIBlock.")
21+
public interface UIBlockViewResolver {
22+
/**
23+
* Resolves a [View] from the given react tag.
24+
*
25+
* @param reactTag the react tag of the view to resolve
26+
* @return the Android [View] instance for the given tag or null if the view is not found
27+
*/
28+
public fun resolveView(reactTag: Int): View?
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
package com.facebook.react.fabric.internal.interop
9+
10+
import com.facebook.testutils.fakes.FakeUIManager
11+
import org.junit.Assert.assertEquals
12+
import org.junit.Test
13+
14+
class InteropUiBlockListenerTest {
15+
16+
@Test
17+
fun prependUIBlock_addsBlockCorrectly() {
18+
val underTest = InteropUIBlockListener()
19+
underTest.prependUIBlock {}
20+
21+
assertEquals(1, underTest.beforeUIBlocks.size)
22+
assertEquals(0, underTest.afterUIBlocks.size)
23+
}
24+
25+
@Test
26+
fun addUIBlock_addsBlockCorrectly() {
27+
val underTest = InteropUIBlockListener()
28+
underTest.addUIBlock {}
29+
30+
assertEquals(0, underTest.beforeUIBlocks.size)
31+
assertEquals(1, underTest.afterUIBlocks.size)
32+
}
33+
34+
@Test
35+
fun willMountItems_emptiesBeforeUIBlocks() {
36+
val underTest = InteropUIBlockListener()
37+
underTest.prependUIBlock {}
38+
underTest.addUIBlock {}
39+
40+
underTest.willMountItems(FakeUIManager())
41+
42+
assertEquals(0, underTest.beforeUIBlocks.size)
43+
assertEquals(1, underTest.afterUIBlocks.size)
44+
}
45+
46+
@Test
47+
fun didMountItems_emptiesAfterUIBlocks() {
48+
val underTest = InteropUIBlockListener()
49+
underTest.prependUIBlock {}
50+
underTest.addUIBlock {}
51+
52+
underTest.didMountItems(FakeUIManager())
53+
54+
assertEquals(1, underTest.beforeUIBlocks.size)
55+
assertEquals(0, underTest.afterUIBlocks.size)
56+
}
57+
58+
@Test
59+
fun willMountItems_deliversUiManagerCorrectly() {
60+
val fakeUIManager = FakeUIManager()
61+
val underTest = InteropUIBlockListener()
62+
63+
underTest.prependUIBlock { uiManager -> uiManager.resolveView(0) }
64+
65+
underTest.willMountItems(fakeUIManager)
66+
67+
assertEquals(1, fakeUIManager.resolvedViewCount)
68+
}
69+
70+
@Test
71+
fun didMountItems_deliversUiManagerCorrectly() {
72+
val fakeUIManager = FakeUIManager()
73+
val underTest = InteropUIBlockListener()
74+
75+
underTest.addUIBlock { uiManager -> uiManager.resolveView(0) }
76+
77+
underTest.didMountItems(fakeUIManager)
78+
79+
assertEquals(1, fakeUIManager.resolvedViewCount)
80+
}
81+
}

0 commit comments

Comments
 (0)