Add support for async callbacks in ReactFindViewUtil

Reviewed By: AaaChiuuu

Differential Revision: D4841500

fbshipit-source-id: 16620d72bd636ad13085c15c38862e16da6c42d2
This commit is contained in:
Andrew Y. Chen 2017-04-11 10:23:22 -07:00 committed by Facebook Github Bot
parent 8fc3b48c65
commit 21819f1a99
4 changed files with 135 additions and 11 deletions

View File

@ -37,9 +37,28 @@ public class NativeIdTestCase extends ReactAppInstrumentationTestCase {
"TextInput", "TextInput",
"View"); "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() { public void testPropertyIsSetForViews() {
for (String nativeId : viewTags) { for (String nativeId : viewTags) {
View viewWithTag = ReactFindViewUtil.findViewByNativeId( View viewWithTag = ReactFindViewUtil.findView(
getActivity().getRootView(), getActivity().getRootView(),
nativeId); nativeId);
assertNotNull( assertNotNull(
@ -47,4 +66,28 @@ public class NativeIdTestCase extends ReactAppInstrumentationTestCase {
viewWithTag); 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);
}
} }

View File

@ -26,6 +26,7 @@ android_library(
react_native_target("java/com/facebook/react/modules/i18nmanager:i18nmanager"), 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/touch:touch"),
react_native_target("java/com/facebook/react/uimanager/annotations:annotations"), 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"), react_native_target("res:uimanager"),
], ],
) )

View File

@ -5,9 +5,11 @@ package com.facebook.react.uimanager;
import android.graphics.Color; import android.graphics.Color;
import android.os.Build; import android.os.Build;
import android.view.View; import android.view.View;
import com.facebook.react.R; import com.facebook.react.R;
import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.uimanager.annotations.ReactProp; 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}. * Base class that should be suitable for the majority of subclasses of {@link ViewManager}.
@ -96,6 +98,7 @@ public abstract class BaseViewManager<T extends View, C extends LayoutShadowNode
@ReactProp(name = PROP_NATIVE_ID) @ReactProp(name = PROP_NATIVE_ID)
public void setNativeId(T view, String nativeId) { public void setNativeId(T view, String nativeId) {
view.setTag(R.id.view_tag_native_id, nativeId); view.setTag(R.id.view_tag_native_id, nativeId);
ReactFindViewUtil.notifyViewRendered(view);
} }
@ReactProp(name = PROP_ACCESSIBILITY_LABEL) @ReactProp(name = PROP_ACCESSIBILITY_LABEL)

View File

@ -4,6 +4,10 @@ package com.facebook.react.uimanager.util;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -14,25 +18,98 @@ import com.facebook.react.R;
*/ */
public class ReactFindViewUtil { public class ReactFindViewUtil {
private static final List<OnViewFoundListener> 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) { public interface OnViewFoundListener {
Object tag = view.getTag(R.id.view_tag_native_id);
if (tag instanceof String && tag.equals(nativeId)) { /**
return view; * Returns the native id of the view of interest
*/
String getNativeId();
/**
* Called when the view has been found
* @param view
*/
void onViewFound(View view);
} }
if (view instanceof ViewGroup) { /**
ViewGroup viewGroup = (ViewGroup) 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 (root instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup) root;
for (int i = 0; i < viewGroup.getChildCount(); i++) { for (int i = 0; i < viewGroup.getChildCount(); i++) {
View v = findViewByNativeId(viewGroup.getChildAt(i), nativeId); View view = findView(viewGroup.getChildAt(i), nativeId);
if (v != null) { if (view != null) {
return v; return view;
} }
} }
} }
return null; 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<OnViewFoundListener> 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;
}
} }