Skip to content

Commit

Permalink
Fix DisplayMetrics KeyboardListener dependency
Browse files Browse the repository at this point in the history
Summary:
public
KeyboardListener needs DisplayMetrics to be initialized when it is attached. At
the moment, this breaks easily whenever we change these components, since DisplayMetrics are intialized
in a module and KeyboardListener is created eagerly in ReactRootView, whereas
ReactRootView can exist without the instance.
This changes to create DisplayMetrics as soon as possible, when the react
instance is built. The KeyboardListener is created and attached after the ReactRootView is
attached to an existing instance, point at which DisplayMetrics have to be
initialized.

Reviewed By: dmmiller

Differential Revision: D2911351

fb-gh-sync-id: 64d1805c5d5b2f6876adb694b565a2df059b381d
  • Loading branch information
andreicoman11 authored and facebook-github-bot-5 committed Feb 8, 2016
1 parent 6b74535 commit 4254e8a
Show file tree
Hide file tree
Showing 11 changed files with 86 additions and 77 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

import javax.annotation.Nullable;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
Expand All @@ -21,8 +23,12 @@
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.View;
import android.view.WindowManager;

import com.facebook.common.logging.FLog;
import com.facebook.infer.annotation.Assertions;
Expand Down Expand Up @@ -58,6 +64,7 @@
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.uimanager.AppRegistry;
import com.facebook.react.uimanager.DisplayMetricsHolder;
import com.facebook.react.uimanager.UIImplementationProvider;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.uimanager.ViewManager;
Expand Down Expand Up @@ -261,6 +268,7 @@ public T get() throws Exception {

// TODO(9577825): remove this
ApplicationHolder.setApplication((Application) applicationContext.getApplicationContext());
setDisplayMetrics(applicationContext);

mApplicationContext = applicationContext;
mJSBundleFile = jsBundleFile;
Expand Down Expand Up @@ -299,6 +307,38 @@ private static void initializeSoLoaderIfNecessary(Context applicationContext) {
SoLoader.init(applicationContext, /* native exopackage */ false);
}

private static void setDisplayMetrics(Context context) {
DisplayMetrics displayMetrics = new DisplayMetrics();
displayMetrics.setTo(context.getResources().getDisplayMetrics());
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();

// Get the real display metrics if we are using API level 17 or higher.
// The real metrics include system decor elements (e.g. soft menu bar).
//
// See: http://developer.android.com/reference/android/view/Display.html#getRealMetrics(android.util.DisplayMetrics)
if (Build.VERSION.SDK_INT >= 17){
display.getRealMetrics(displayMetrics);

} else {
// For 14 <= API level <= 16, we need to invoke getRawHeight and getRawWidth to get the real dimensions.
// Since react-native only supports API level 16+ we don't have to worry about other cases.
//
// Reflection exceptions are rethrown at runtime.
//
// See: http://stackoverflow.com/questions/14341041/how-to-get-real-screen-height-and-width/23861333#23861333
try {
Method mGetRawH = Display.class.getMethod("getRawHeight");
Method mGetRawW = Display.class.getMethod("getRawWidth");
displayMetrics.widthPixels = (Integer) mGetRawW.invoke(display);
displayMetrics.heightPixels = (Integer) mGetRawH.invoke(display);
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
throw new RuntimeException("Error getting real dimensions for API level < 17", e);
}
}
DisplayMetricsHolder.setDisplayMetrics(displayMetrics);
}

/**
* Trigger react context initialization asynchronously in a background async task. This enables
* applications to pre-load the application JS, and execute global code before
Expand Down
23 changes: 17 additions & 6 deletions ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,10 @@
*/
public class ReactRootView extends SizeMonitoringFrameLayout implements RootView {

private final KeyboardListener mKeyboardListener = new KeyboardListener();

private @Nullable ReactInstanceManager mReactInstanceManager;
private @Nullable String mJSModuleName;
private @Nullable Bundle mLaunchOptions;
private @Nullable KeyboardListener mKeyboardListener;
private int mTargetTag = -1;
private final float[] mTargetCoordinates = new float[2];
private boolean mChildIsHandlingNativeGesture = false;
Expand Down Expand Up @@ -107,7 +106,7 @@ public void run() {
Assertions.assertNotNull(mReactInstanceManager)
.attachMeasuredRootView(ReactRootView.this);
mIsAttachedToInstance = true;
getViewTreeObserver().addOnGlobalLayoutListener(mKeyboardListener);
getViewTreeObserver().addOnGlobalLayoutListener(getKeyboardListener());
}
});
}
Expand Down Expand Up @@ -298,7 +297,7 @@ protected void onDetachedFromWindow() {
if (mReactInstanceManager != null && !mAttachScheduled) {
mReactInstanceManager.detachRootView(this);
mIsAttachedToInstance = false;
getViewTreeObserver().removeOnGlobalLayoutListener(mKeyboardListener);
getViewTreeObserver().removeOnGlobalLayoutListener(getKeyboardListener());
}
}

Expand Down Expand Up @@ -356,7 +355,7 @@ public void startReactApplication(
if (mWasMeasured && mIsAttachedToWindow) {
mReactInstanceManager.attachMeasuredRootView(this);
mIsAttachedToInstance = true;
getViewTreeObserver().addOnGlobalLayoutListener(mKeyboardListener);
getViewTreeObserver().addOnGlobalLayoutListener(getKeyboardListener());
} else {
mAttachScheduled = true;
}
Expand All @@ -381,9 +380,21 @@ public void startReactApplication(
mWasMeasured = true;
}

private KeyboardListener getKeyboardListener() {
if (mKeyboardListener == null) {
mKeyboardListener = new KeyboardListener();
}
return mKeyboardListener;
}

private class KeyboardListener implements ViewTreeObserver.OnGlobalLayoutListener {
private final Rect mVisibleViewArea;

private int mKeyboardHeight = 0;
private final Rect mVisibleViewArea = new Rect();

/* package */ KeyboardListener() {
mVisibleViewArea = new Rect();
}

@Override
public void onGlobalLayout() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,10 @@

import javax.annotation.Nullable;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

import android.content.Context;
import android.os.Build;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.WindowManager;

import com.facebook.csslayout.CSSLayoutContext;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.animation.Animation;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.bridge.OnBatchCompleteListener;
Expand All @@ -35,7 +23,6 @@
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.uimanager.debug.NotThreadSafeViewHierarchyUpdateDebugListener;
import com.facebook.react.uimanager.events.EventDispatcher;
import com.facebook.systrace.Systrace;
Expand Down Expand Up @@ -90,11 +77,7 @@ public UIManagerModule(
UIImplementation uiImplementation) {
super(reactContext);
mEventDispatcher = new EventDispatcher(reactContext);

DisplayMetrics displayMetrics = getDisplayMetrics();

DisplayMetricsHolder.setDisplayMetrics(displayMetrics);
mModuleConstants = createConstants(displayMetrics, viewManagerList);
mModuleConstants = createConstants(viewManagerList);
mUIImplementation = uiImplementation;

reactContext.addLifecycleEventListener(this);
Expand Down Expand Up @@ -131,14 +114,10 @@ public void onCatalystInstanceDestroy() {
mEventDispatcher.onCatalystInstanceDestroyed();
}

private static Map<String, Object> createConstants(
DisplayMetrics displayMetrics,
List<ViewManager> viewManagerList) {
private static Map<String, Object> createConstants(List<ViewManager> viewManagerList) {
Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "CreateUIManagerConstants");
try {
return UIManagerModuleConstantsHelper.createConstants(
displayMetrics,
viewManagerList);
return UIManagerModuleConstantsHelper.createConstants(viewManagerList);
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}
Expand Down Expand Up @@ -460,40 +439,4 @@ public EventDispatcher getEventDispatcher() {
public void sendAccessibilityEvent(int tag, int eventType) {
mUIImplementation.sendAccessibilityEvent(tag, eventType);
}

private DisplayMetrics getDisplayMetrics() {
Context context = getReactApplicationContext();

DisplayMetrics displayMetrics = new DisplayMetrics();
displayMetrics.setTo(context.getResources().getDisplayMetrics());
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();

// Get the real display metrics if we are using API level 17 or higher.
// The real metrics include system decor elements (e.g. soft menu bar).
//
// See: http://developer.android.com/reference/android/view/Display.html#getRealMetrics(android.util.DisplayMetrics)
if (Build.VERSION.SDK_INT >= 17){
display.getRealMetrics(displayMetrics);

} else {
// For 14 <= API level <= 16, we need to invoke getRawHeight and getRawWidth to get the real dimensions.
// Since react-native only supports API level 16+ we don't have to worry about other cases.
//
// Reflection exceptions are rethrown at runtime.
//
// See: http://stackoverflow.com/questions/14341041/how-to-get-real-screen-height-and-width/23861333#23861333
try {
Method mGetRawH = Display.class.getMethod("getRawHeight");
Method mGetRawW = Display.class.getMethod("getRawWidth");
displayMetrics.widthPixels = (Integer) mGetRawW.invoke(display);
displayMetrics.heightPixels = (Integer) mGetRawH.invoke(display);
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
throw new RuntimeException("Error getting real dimensions for API level < 17", e);
}
}

return displayMetrics;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
import java.util.HashMap;
import java.util.Map;

import android.text.InputType;
import android.util.DisplayMetrics;
import android.view.accessibility.AccessibilityEvent;
import android.widget.ImageView;
Expand Down Expand Up @@ -80,7 +79,7 @@
.build();
}

public static Map<String, Object> getConstants(DisplayMetrics displayMetrics) {
public static Map<String, Object> getConstants() {
HashMap<String, Object> constants = new HashMap<String, Object>();
constants.put(
"UIView",
Expand All @@ -92,6 +91,7 @@ public static Map<String, Object> getConstants(DisplayMetrics displayMetrics) {
"ScaleAspectFill",
ImageView.ScaleType.CENTER_CROP.ordinal())));

DisplayMetrics displayMetrics = DisplayMetricsHolder.getDisplayMetrics();
constants.put(
"Dimensions",
MapBuilder.of(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,9 @@

package com.facebook.react.uimanager;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import android.util.DisplayMetrics;

import com.facebook.react.common.MapBuilder;

/**
Expand All @@ -39,10 +36,8 @@
* {@link UIManagerModuleConstants}.
* TODO(6845124): Create a test for this
*/
/* package */ static Map<String, Object> createConstants(
DisplayMetrics displayMetrics,
List<ViewManager> viewManagers) {
Map<String, Object> constants = UIManagerModuleConstants.getConstants(displayMetrics);
/* package */ static Map<String, Object> createConstants(List<ViewManager> viewManagers) {
Map<String, Object> constants = UIManagerModuleConstants.getConstants();
Map bubblingEventTypesConstants = UIManagerModuleConstants.getBubblingEventTypeConstants();
Map directEventTypesConstants = UIManagerModuleConstants.getDirectEventTypeConstants();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,17 @@
import android.widget.TextView;

import com.facebook.infer.annotation.Assertions;
import com.facebook.react.bridge.JSApplicationCausedNativeException;
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.uimanager.BaseViewManager;
import com.facebook.react.uimanager.PixelUtil;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.uimanager.ViewDefaults;
import com.facebook.react.uimanager.ViewProps;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.uimanager.events.EventDispatcher;
import com.facebook.react.views.text.DefaultStyleValuesUtil;
import com.facebook.react.views.text.ReactTextUpdate;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import java.util.List;
import java.util.Map;

import android.util.DisplayMetrics;
import android.view.View;

import com.facebook.react.bridge.ReactApplicationContext;
Expand Down Expand Up @@ -145,6 +146,8 @@ public void customIntGroupProp(View v, int index, Integer value) {
public void testNativePropsIncludeCorrectTypes() {
List<ViewManager> viewManagers = Arrays.<ViewManager>asList(new ViewManagerUnderTest());
ReactApplicationContext reactContext = new ReactApplicationContext(RuntimeEnvironment.application);
DisplayMetrics displayMetrics = reactContext.getResources().getDisplayMetrics();
DisplayMetricsHolder.setDisplayMetrics(displayMetrics);
UIManagerModule uiManagerModule = new UIManagerModule(
reactContext,
viewManagers,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import java.util.List;
import java.util.Map;

import android.util.DisplayMetrics;

import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.common.MapBuilder;

Expand Down Expand Up @@ -56,6 +58,9 @@ public class UIManagerModuleConstantsTest {
public void setUp() {
mReactContext = new ReactApplicationContext(RuntimeEnvironment.application);
mUIImplementation = mock(UIImplementation.class);

DisplayMetrics displayMetrics = mReactContext.getResources().getDisplayMetrics();
DisplayMetricsHolder.setDisplayMetrics(displayMetrics);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import java.util.List;

import android.graphics.Color;
import android.util.DisplayMetrics;
import android.view.Choreographer;
import android.view.View;
import android.view.ViewGroup;
Expand Down Expand Up @@ -107,9 +108,13 @@ public Object answer(InvocationOnMock invocation) throws Throwable {
mReactContext = new ReactApplicationContext(RuntimeEnvironment.application);
mReactContext.initializeWithInstance(mCatalystInstanceMock);

DisplayMetrics displayMetrics = mReactContext.getResources().getDisplayMetrics();
DisplayMetricsHolder.setDisplayMetrics(displayMetrics);

UIManagerModule uiManagerModuleMock = mock(UIManagerModule.class);
when(mCatalystInstanceMock.getNativeModule(UIManagerModule.class))
.thenReturn(uiManagerModuleMock);

}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import android.os.Build;
import android.text.Spanned;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.text.style.AbsoluteSizeSpan;
import android.view.Choreographer;
import android.widget.TextView;
Expand All @@ -31,6 +32,7 @@
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.SimpleArray;
import com.facebook.react.bridge.SimpleMap;
import com.facebook.react.uimanager.DisplayMetricsHolder;
import com.facebook.react.uimanager.ReactChoreographer;
import com.facebook.react.uimanager.UIImplementation;
import com.facebook.react.uimanager.UIManagerModule;
Expand Down Expand Up @@ -371,6 +373,8 @@ private void executePendingChoreographerCallbacks() {

public UIManagerModule getUIManagerModule() {
ReactApplicationContext reactContext = ReactTestHelper.createCatalystContextForTest();
DisplayMetrics displayMetrics = reactContext.getResources().getDisplayMetrics();
DisplayMetricsHolder.setDisplayMetrics(displayMetrics);
List<ViewManager> viewManagers = Arrays.asList(
new ViewManager[] {
new ReactTextViewManager(),
Expand Down
Loading

0 comments on commit 4254e8a

Please sign in to comment.