Files
react-native/ReactAndroid/src/main/java/com/facebook/react/ReactActivityDelegate.java
T
Krzysztof Magiera 9f0dede1c9 Allow again for injecting custom root view via ReactActivityDelegate (#26495)
Summary:
This change restores the possibility of injecting custom root views via ReactAcitivtyDelegate. It has been used by react-native-gesture-handler library in order to replace default root view with a one that'd route touch events to gesture-handler internal pipeline.

The regression happened in d0792d4b8a where new `ReactDelegate` was introduced to provide support for rendering react native views in both Android fragments and activities. As a part of that change the logic responsible for creating root view has been moved from `ReactActivityDelegate` to `ReactDelegate` rendering `ReactActivityDelegate.createRootView` unused – that is there is no code path that leads to this method being called. Instead `ReactDelegate.createRootView` method has been added which now plays the same role. The custom root view injection was relying on overwriting that method and hence the method is no longer referenced overwriting it has no effect. Following the logic migration out of `ReactActivityDelegate` into `ReactDelegate` we could potentially now start overwriting methods of `ReactDelegate`. However when working with Android's activities in React Native we can only provide an instance of `ReactActivityDelegate` and in my opinion it does not make too much sense to expose also a way to provide own instance of `ReactDelegate`.

The proposed fix was to route `ReactDelegate.createRootView` to call `ReactActivityDelegate.createRootView` and this way regaining control over root view creation to `ReactActivityDelgate`. The change of the behavior has been implemented by subclassing `ReactDelegate` directly from `ReactActivityDelegate` and hooking the aforementioned methods together. Thanks to this approach, the change has no effect on `ReactDelegate` being used directly from fragments or in other scenarios where it is not being instantiated from `ReactActivityDelegate`.

This fixes an issue reported in https://github.com/kmagiera/react-native-gesture-handler/issues/745 and discussed on 0.61 release thread: https://github.com/react-native-community/releases/issues/140#issuecomment-532235945

## Changelog

[Internal] [Fixed] - Allow for custom root view to be injected via ReactActivityDelegate
Pull Request resolved: https://github.com/facebook/react-native/pull/26495

Test Plan:
1. Run RNTester, take layout snapshot, see the react root view being on the top of view hierarchy.
2. Run gesture-handler Example app (or some other app that overwrites ReactActivityDelegate.createRootView method), on layout snapshot see custom root view being used.

Differential Revision: D17482966

Pulled By: mdvacca

fbshipit-source-id: 866f551b8b077bafe1eb9e34e5dccb1240fa935e
2019-09-19 12:23:08 -07:00

185 lines
5.7 KiB
Java

// Copyright (c) Facebook, Inc. and its affiliates.
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
package com.facebook.react;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.view.KeyEvent;
import androidx.annotation.Nullable;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.bridge.Callback;
import com.facebook.react.modules.core.PermissionListener;
/**
* Delegate class for {@link ReactActivity} and {@link ReactFragmentActivity}. You can subclass this
* to provide custom implementations for e.g. {@link #getReactNativeHost()}, if your Application
* class doesn't implement {@link ReactApplication}.
*/
public class ReactActivityDelegate {
private final @Nullable Activity mActivity;
private final @Nullable String mMainComponentName;
private @Nullable PermissionListener mPermissionListener;
private @Nullable Callback mPermissionsCallback;
private ReactDelegate mReactDelegate;
@Deprecated
public ReactActivityDelegate(Activity activity, @Nullable String mainComponentName) {
mActivity = activity;
mMainComponentName = mainComponentName;
}
public ReactActivityDelegate(ReactActivity activity, @Nullable String mainComponentName) {
mActivity = activity;
mMainComponentName = mainComponentName;
}
protected @Nullable Bundle getLaunchOptions() {
return null;
}
protected ReactRootView createRootView() {
return new ReactRootView(getContext());
}
/**
* Get the {@link ReactNativeHost} used by this app. By default, assumes {@link
* Activity#getApplication()} is an instance of {@link ReactApplication} and calls {@link
* ReactApplication#getReactNativeHost()}. Override this method if your application class does not
* implement {@code ReactApplication} or you simply have a different mechanism for storing a
* {@code ReactNativeHost}, e.g. as a static field somewhere.
*/
protected ReactNativeHost getReactNativeHost() {
return ((ReactApplication) getPlainActivity().getApplication()).getReactNativeHost();
}
public ReactInstanceManager getReactInstanceManager() {
return mReactDelegate.getReactInstanceManager();
}
public String getMainComponentName() {
return mMainComponentName;
}
protected void onCreate(Bundle savedInstanceState) {
String mainComponentName = getMainComponentName();
mReactDelegate =
new ReactDelegate(
getPlainActivity(), getReactNativeHost(), mainComponentName, getLaunchOptions()) {
@Override
protected ReactRootView createRootView() {
return ReactActivityDelegate.this.createRootView();
}
};
if (mMainComponentName != null) {
loadApp(mainComponentName);
}
}
protected void loadApp(String appKey) {
mReactDelegate.loadApp(appKey);
getPlainActivity().setContentView(mReactDelegate.getReactRootView());
}
protected void onPause() {
mReactDelegate.onHostPause();
}
protected void onResume() {
mReactDelegate.onHostResume();
if (mPermissionsCallback != null) {
mPermissionsCallback.invoke();
mPermissionsCallback = null;
}
}
protected void onDestroy() {
mReactDelegate.onHostDestroy();
}
public void onActivityResult(int requestCode, int resultCode, Intent data) {
mReactDelegate.onActivityResult(requestCode, resultCode, data, true);
}
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (getReactNativeHost().hasInstance()
&& getReactNativeHost().getUseDeveloperSupport()
&& keyCode == KeyEvent.KEYCODE_MEDIA_FAST_FORWARD) {
event.startTracking();
return true;
}
return false;
}
public boolean onKeyUp(int keyCode, KeyEvent event) {
return mReactDelegate.shouldShowDevMenuOrReload(keyCode, event);
}
public boolean onKeyLongPress(int keyCode, KeyEvent event) {
if (getReactNativeHost().hasInstance()
&& getReactNativeHost().getUseDeveloperSupport()
&& keyCode == KeyEvent.KEYCODE_MEDIA_FAST_FORWARD) {
getReactNativeHost().getReactInstanceManager().showDevOptionsDialog();
return true;
}
return false;
}
public boolean onBackPressed() {
return mReactDelegate.onBackPressed();
}
public boolean onNewIntent(Intent intent) {
if (getReactNativeHost().hasInstance()) {
getReactNativeHost().getReactInstanceManager().onNewIntent(intent);
return true;
}
return false;
}
public void onWindowFocusChanged(boolean hasFocus) {
if (getReactNativeHost().hasInstance()) {
getReactNativeHost().getReactInstanceManager().onWindowFocusChange(hasFocus);
}
}
@TargetApi(Build.VERSION_CODES.M)
public void requestPermissions(
String[] permissions, int requestCode, PermissionListener listener) {
mPermissionListener = listener;
getPlainActivity().requestPermissions(permissions, requestCode);
}
public void onRequestPermissionsResult(
final int requestCode, final String[] permissions, final int[] grantResults) {
mPermissionsCallback =
new Callback() {
@Override
public void invoke(Object... args) {
if (mPermissionListener != null
&& mPermissionListener.onRequestPermissionsResult(
requestCode, permissions, grantResults)) {
mPermissionListener = null;
}
}
};
}
protected Context getContext() {
return Assertions.assertNotNull(mActivity);
}
protected Activity getPlainActivity() {
return ((Activity) getContext());
}
}