diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/tests/NativeIdTestCase.java b/ReactAndroid/src/androidTest/java/com/facebook/react/tests/NativeIdTestCase.java index cccec2ae1..e547fed89 100644 --- a/ReactAndroid/src/androidTest/java/com/facebook/react/tests/NativeIdTestCase.java +++ b/ReactAndroid/src/androidTest/java/com/facebook/react/tests/NativeIdTestCase.java @@ -37,9 +37,28 @@ public class NativeIdTestCase extends ReactAppInstrumentationTestCase { "TextInput", "View"); + private boolean mViewFound; + + @Override + protected void setUp() throws Exception { + mViewFound = false; + ReactFindViewUtil.addViewListener(new ReactFindViewUtil.OnViewFoundListener() { + @Override + public String getNativeId() { + return viewTags.get(0); + } + + @Override + public void onViewFound(View view) { + mViewFound = true; + } + }); + super.setUp(); + } + public void testPropertyIsSetForViews() { for (String nativeId : viewTags) { - View viewWithTag = ReactFindViewUtil.findViewByNativeId( + View viewWithTag = ReactFindViewUtil.findView( getActivity().getRootView(), nativeId); assertNotNull( @@ -47,4 +66,28 @@ public class NativeIdTestCase extends ReactAppInstrumentationTestCase { viewWithTag); } } + + public void testViewListener() { + assertTrue("OnViewFound callback was never invoked", mViewFound); + } + + public void testFindView() { + mViewFound = false; + ReactFindViewUtil.findView( + getActivity().getRootView(), + new ReactFindViewUtil.OnViewFoundListener() { + @Override + public String getNativeId() { + return viewTags.get(0); + } + + @Override + public void onViewFound(View view) { + mViewFound = true; + } + }); + assertTrue( + "OnViewFound callback should have successfully been invoked synchronously", + mViewFound); + } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/BUCK b/ReactAndroid/src/main/java/com/facebook/react/uimanager/BUCK index c75b10b0d..4744c89a9 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/BUCK @@ -26,6 +26,7 @@ android_library( react_native_target("java/com/facebook/react/modules/i18nmanager:i18nmanager"), react_native_target("java/com/facebook/react/touch:touch"), react_native_target("java/com/facebook/react/uimanager/annotations:annotations"), + react_native_target("java/com/facebook/react/uimanager/util:util"), react_native_target("res:uimanager"), ], ) diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java index 271b126b3..86b48444b 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java @@ -5,9 +5,11 @@ package com.facebook.react.uimanager; import android.graphics.Color; import android.os.Build; import android.view.View; + import com.facebook.react.R; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.uimanager.annotations.ReactProp; +import com.facebook.react.uimanager.util.ReactFindViewUtil; /** * Base class that should be suitable for the majority of subclasses of {@link ViewManager}. @@ -96,6 +98,7 @@ public abstract class BaseViewManager mOnViewFoundListeners = new ArrayList<>(); + /** - * Finds a view that is tagged with {@param nativeId} as its `nativeID` prop + * Callback to be invoked when a react native view has been found */ - public static @Nullable View findViewByNativeId(View view, String nativeId) { - Object tag = view.getTag(R.id.view_tag_native_id); - if (tag instanceof String && tag.equals(nativeId)) { - return view; + public interface OnViewFoundListener { + + /** + * Returns the native id of the view of interest + */ + String getNativeId(); + + /** + * Called when the view has been found + * @param view + */ + void onViewFound(View view); + } + + /** + * Finds a view that is tagged with {@param nativeId} as its nativeID prop + * under the {@param root} view hierarchy. Returns the view if found, null otherwise. + * @param root root of the view hierarchy from which to find the view + */ + public static @Nullable View findView(View root, String nativeId) { + String tag = getNativeId(root); + if (tag != null && tag.equals(nativeId)) { + return root; } - if (view instanceof ViewGroup) { - ViewGroup viewGroup = (ViewGroup) view; + if (root instanceof ViewGroup) { + ViewGroup viewGroup = (ViewGroup) root; for (int i = 0; i < viewGroup.getChildCount(); i++) { - View v = findViewByNativeId(viewGroup.getChildAt(i), nativeId); - if (v != null) { - return v; + View view = findView(viewGroup.getChildAt(i), nativeId); + if (view != null) { + return view; } } } return null; } + + /** + * Finds a view tagged with {@param onViewFoundListener}'s nativeID in the given {@param root} + * view hierarchy. If the view does not exist yet due to React Native's async layout, a listener + * will be added. When the view is found, the {@param onViewFoundListener} will be invoked. + * @param root root of the view hierarchy from which to find the view + */ + public static void findView(View root, OnViewFoundListener onViewFoundListener) { + View view = findView(root, onViewFoundListener.getNativeId()); + if (view != null) { + onViewFoundListener.onViewFound(view); + } + addViewListener(onViewFoundListener); + } + + /** + * Registers an OnViewFoundListener to be invoked when a view with a matching nativeID is found. + * Remove this listener using removeViewListener() if it's no longer needed. + */ + public static void addViewListener(OnViewFoundListener onViewFoundListener) { + mOnViewFoundListeners.add(onViewFoundListener); + } + + /** + * Removes an OnViewFoundListener previously registered with addViewListener(). + */ + public static void removeViewListener(OnViewFoundListener onViewFoundListener) { + mOnViewFoundListeners.remove(onViewFoundListener); + } + + /** + * Invokes any listeners that are listening on this {@param view}'s native id + */ + public static void notifyViewRendered(View view) { + String nativeId = getNativeId(view); + if (nativeId == null) { + return; + } + Iterator iterator = mOnViewFoundListeners.iterator(); + while (iterator.hasNext()) { + OnViewFoundListener listener = iterator.next(); + if (nativeId != null && nativeId.equals(listener.getNativeId())) { + listener.onViewFound(view); + iterator.remove(); + } + } + } + + private static @Nullable String getNativeId(View view) { + Object tag = view.getTag(R.id.view_tag_native_id); + return tag instanceof String ? (String) tag : null; + } }