-
Notifications
You must be signed in to change notification settings - Fork 24.9k
Android - Add a ReactFragment #12199
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
06d19bd
eec52b9
b9803ed
99028ba
c46baad
b6983b7
b089982
e5b6871
0bcc115
5e10b8a
bafd3b0
5451b53
e0330a0
a6aa4a7
7b4f155
1b013f9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
/** | ||
* 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.app.Activity; | ||
import android.content.Intent; | ||
import android.os.Bundle; | ||
import android.view.KeyEvent; | ||
|
||
import com.facebook.infer.annotation.Assertions; | ||
import com.facebook.react.devsupport.DoubleTapReloadRecognizer; | ||
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler; | ||
|
||
import javax.annotation.Nullable; | ||
|
||
/** | ||
* A delegate for handling React Application support. This delegate is unaware whether it is used in | ||
* an {@link Activity} or a {@link android.app.Fragment}. | ||
*/ | ||
public class ReactDelegate { | ||
|
||
private final Activity mActivity; | ||
private ReactRootView mReactRootView; | ||
|
||
@Nullable | ||
private final String mMainComponentName; | ||
|
||
@Nullable | ||
private Bundle mLaunchOptions; | ||
|
||
@Nullable | ||
private DoubleTapReloadRecognizer mDoubleTapReloadRecognizer; | ||
|
||
|
||
public ReactDelegate(Activity activity, @Nullable String appKey, @Nullable Bundle launchOptions) { | ||
mActivity = activity; | ||
mMainComponentName = appKey; | ||
mLaunchOptions = launchOptions; | ||
mDoubleTapReloadRecognizer = new DoubleTapReloadRecognizer(); | ||
} | ||
|
||
public void onHostResume() { | ||
if (getReactNativeHost().hasInstance()) { | ||
if (mActivity instanceof DefaultHardwareBackBtnHandler) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This forces the host activity to implement the interface, when in fact it's already implemented by the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good call out. How would you see this working then? Extra param in the constructor to pass a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That would be my first choice. I wonder if the the behaviour would be the same, since There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm actually implementing react-navigation this week in a project that will make use of the back button login, in this exact fragment class, so I can tweak it a bit to figure out what feels best. But I think I agree with what you're saying above. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @serj-lotutovici So I updated the code a bit in b089982 After messing with fragment navigations stacks this week, it seemed easier to allow the Activity to handle the back navigation. If the Fragment handled the back button handler, it ended up getting stuck in a loop, or it wasn't able to safely call the This will be shown via documentation for sure, but I'll work on documentation if/when we get this PR "approved" and the idea is solidified. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @jpshelley I agree on the importance of Fragment support. For example some components like ViewPager return Fragments for each view. This is standard. Btw, are there tests for the Fragment solution or do you have an example app that is using these modifications? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We are currently in the process of open sourcing our solution at Hudl. Since contributing back to React Native seems to be taking longer then expected, we wanted to be able to share our approach with others. I will comment back on this PR when that happens. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sounds great! I am currently doing React Native component integration into Native app and would like to try it out when available. |
||
getReactNativeHost().getReactInstanceManager().onHostResume(mActivity, (DefaultHardwareBackBtnHandler) mActivity); | ||
} else { | ||
throw new ClassCastException("Host Activity does not implement DefaultHardwareBackBtnHandler"); | ||
} | ||
} | ||
} | ||
|
||
public void onHostPause() { | ||
if (getReactNativeHost().hasInstance()) { | ||
getReactNativeHost().getReactInstanceManager().onHostPause(mActivity); | ||
} | ||
} | ||
|
||
public void onHostDestroy() { | ||
if (mReactRootView != null) { | ||
mReactRootView.unmountReactApplication(); | ||
mReactRootView = null; | ||
} | ||
if (getReactNativeHost().hasInstance()) { | ||
getReactNativeHost().getReactInstanceManager().onHostDestroy(mActivity); | ||
} | ||
} | ||
|
||
public boolean onBackPressed() { | ||
if (getReactNativeHost().hasInstance()) { | ||
getReactNativeHost().getReactInstanceManager().onBackPressed(); | ||
return true; | ||
} | ||
return false; | ||
} | ||
|
||
public void onActivityResult(int requestCode, int resultCode, Intent data, boolean shouldForwardToReactInstance) { | ||
if (getReactNativeHost().hasInstance() && shouldForwardToReactInstance) { | ||
getReactNativeHost().getReactInstanceManager().onActivityResult(mActivity, requestCode, resultCode, data); | ||
} | ||
} | ||
|
||
public void loadApp() { | ||
loadApp(mMainComponentName); | ||
} | ||
|
||
public void loadApp(String appKey) { | ||
if (mReactRootView != null) { | ||
throw new IllegalStateException("Cannot loadApp while app is already running."); | ||
} | ||
mReactRootView = new ReactRootView(mActivity); | ||
mReactRootView.startReactApplication( | ||
getReactNativeHost().getReactInstanceManager(), | ||
appKey, | ||
mLaunchOptions); | ||
|
||
} | ||
|
||
public ReactRootView getReactRootView() { | ||
return mReactRootView; | ||
} | ||
|
||
/** | ||
* Handles delegating the {@link Activity#onKeyUp(int, KeyEvent)} method to determine whether | ||
* the application should show the developer menu or should reload the React Application. | ||
* | ||
* @return true if we consume the event and either shoed the develop menu or reloaded the application. | ||
*/ | ||
public boolean shouldShowDevMenuOrReload(int keyCode, KeyEvent event) { | ||
if (getReactNativeHost().hasInstance() && getReactNativeHost().getUseDeveloperSupport()) { | ||
if (keyCode == KeyEvent.KEYCODE_MENU) { | ||
getReactNativeHost().getReactInstanceManager().showDevOptionsDialog(); | ||
return true; | ||
} | ||
boolean didDoubleTapR = Assertions.assertNotNull(mDoubleTapReloadRecognizer).didDoubleTapR(keyCode, mActivity.getCurrentFocus()); | ||
if (didDoubleTapR) { | ||
getReactNativeHost().getReactInstanceManager().getDevSupportManager().handleReloadJS(); | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
|
||
/** | ||
* 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) mActivity.getApplication()).getReactNativeHost(); | ||
} | ||
|
||
public ReactInstanceManager getReactInstanceManager() { | ||
return getReactNativeHost().getReactInstanceManager(); | ||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Before looking to merge this - does it make sense to have a
ReactActivityDelegate
and then aReactDelegate
or does it make sense forReactActivity
to use theReactDelegate
directly? Was trying to bring smaller changes with this PR and focus primarily on the addition of theReactFragment
but also don't want to incur possible Tech Debt