Skip to content

Commit

Permalink
Add ReactInstanceHolder
Browse files Browse the repository at this point in the history
Reviewed By: astreet

Differential Revision: D3328342

fbshipit-source-id: af4e825d0b7c2d3d4490094a939e97cc527dd242
  • Loading branch information
foghina authored and Facebook Github Bot 8 committed Jun 17, 2016
1 parent d78602b commit 49f20f4
Show file tree
Hide file tree
Showing 6 changed files with 219 additions and 115 deletions.
130 changes: 40 additions & 90 deletions ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,38 +40,8 @@ public abstract class ReactActivity extends Activity
private @Nullable PermissionListener mPermissionListener;
private @Nullable ReactInstanceManager mReactInstanceManager;
private @Nullable ReactRootView mReactRootView;
private LifecycleState mLifecycleState = LifecycleState.BEFORE_RESUME;
private DoubleTapReloadRecognizer mDoubleTapReloadRecognizer;

/**
* Returns the name of the bundle in assets. If this is null, and no file path is specified for
* the bundle, the app will only work with {@code getUseDeveloperSupport} enabled and will
* always try to load the JS bundle from the packager server.
* e.g. "index.android.bundle"
*/
protected @Nullable String getBundleAssetName() {
return "index.android.bundle";
};

/**
* Returns a custom path of the bundle file. This is used in cases the bundle should be loaded
* from a custom path. By default it is loaded from Android assets, from a path specified
* by {@link getBundleAssetName}.
* e.g. "file://sdcard/myapp_cache/index.android.bundle"
*/
protected @Nullable String getJSBundleFile() {
return null;
}

/**
* Returns the name of the main module. Determines the URL used to fetch the JS bundle
* from the packager server. It is only used when dev support is enabled.
* This is the first file to be executed once the {@link ReactInstanceManager} is created.
* e.g. "index.android"
*/
protected String getJSMainModuleName() {
return "index.android";
}
private boolean mDoRefresh = false;

/**
* Returns the launchOptions which will be passed to the {@link ReactInstanceManager}
Expand All @@ -92,48 +62,31 @@ protected String getJSMainModuleName() {
protected abstract String getMainComponentName();

/**
* Returns whether dev mode should be enabled. This enables e.g. the dev menu.
*/
protected abstract boolean getUseDeveloperSupport();

/**
* Returns a list of {@link ReactPackage} used by the app.
* You'll most likely want to return at least the {@code MainReactPackage}.
* If your app uses additional views or modules besides the default ones,
* you'll want to include more packages here.
* A subclass may override this method if it needs to use a custom {@link ReactRootView}.
*/
protected abstract List<ReactPackage> getPackages();
protected ReactRootView createRootView() {
return new ReactRootView(this);
}

/**
* A subclass may override this method if it needs to use a custom instance.
* Get the {@link ReactNativeHost} used by this app. By default, assumes {@link #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 ReactInstanceManager createReactInstanceManager() {
ReactInstanceManager.Builder builder = ReactInstanceManager.builder()
.setApplication(getApplication())
.setJSMainModuleName(getJSMainModuleName())
.setUseDeveloperSupport(getUseDeveloperSupport())
.setInitialLifecycleState(mLifecycleState);

for (ReactPackage reactPackage : getPackages()) {
builder.addPackage(reactPackage);
}

String jsBundleFile = getJSBundleFile();

if (jsBundleFile != null) {
builder.setJSBundleFile(jsBundleFile);
} else {
builder.setBundleAssetName(getBundleAssetName());
}

return builder.build();
protected ReactNativeHost getReactNativeHost() {
return ((ReactApplication) getApplication()).getReactNativeHost();
}

/**
* A subclass may override this method if it needs to use a custom {@link ReactRootView}.
* Get whether developer support should be enabled or not. By default this delegates to
* {@link ReactNativeHost#getUseDeveloperSupport()}. Override this method if your application
* class does not implement {@code ReactApplication} or you simply have a different logic for
* determining this (default just checks {@code BuildConfig}).
*/
protected ReactRootView createRootView() {
return new ReactRootView(this);
protected boolean getUseDeveloperSupport() {
return ((ReactApplication) getApplication()).getReactNativeHost().getUseDeveloperSupport();

This comment has been minimized.

Copy link
@baoti

baoti Jun 18, 2016

Call this.getReactNativeHost() instead.

This comment has been minimized.

Copy link
@baoti

baoti Jun 29, 2016

@foghina , If it call getReactNativeHost(), I just need override one getReactNativeHost(), no need for getUseDeveloperSupport().

}

@Override
Expand All @@ -150,9 +103,11 @@ protected void onCreate(Bundle savedInstanceState) {
}
}

mReactInstanceManager = createReactInstanceManager();
mReactRootView = createRootView();
mReactRootView.startReactApplication(mReactInstanceManager, getMainComponentName(), getLaunchOptions());
mReactRootView.startReactApplication(
getReactNativeHost().getReactInstanceManager(),
getMainComponentName(),
getLaunchOptions());
setContentView(mReactRootView);
mDoubleTapReloadRecognizer = new DoubleTapReloadRecognizer();
}
Expand All @@ -161,62 +116,57 @@ protected void onCreate(Bundle savedInstanceState) {
protected void onPause() {
super.onPause();

mLifecycleState = LifecycleState.BEFORE_RESUME;

if (mReactInstanceManager != null) {
mReactInstanceManager.onHostPause();
if (getReactNativeHost().hasInstance()) {
getReactNativeHost().getReactInstanceManager().onHostPause();
}
}

@Override
protected void onResume() {
super.onResume();

mLifecycleState = LifecycleState.RESUMED;

if (mReactInstanceManager != null) {
mReactInstanceManager.onHostResume(this, this);
if (getReactNativeHost().hasInstance()) {
getReactNativeHost().getReactInstanceManager().onHostResume(this, this);
}
}

@Override
protected void onDestroy() {
super.onDestroy();

mReactRootView.unmountReactApplication();
mReactRootView = null;

if (mReactInstanceManager != null) {
mReactInstanceManager.destroy();
if (mReactRootView != null) {
mReactRootView.unmountReactApplication();
mReactRootView = null;
}
getReactNativeHost().clear();
}

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (mReactInstanceManager != null) {
mReactInstanceManager.onActivityResult(requestCode, resultCode, data);
if (getReactNativeHost().hasInstance()) {
getReactNativeHost().getReactInstanceManager()
.onActivityResult(requestCode, resultCode, data);
}
}

@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (mReactInstanceManager != null &&
mReactInstanceManager.getDevSupportManager().getDevSupportEnabled()) {
if (getReactNativeHost().hasInstance() && getUseDeveloperSupport()) {
if (keyCode == KeyEvent.KEYCODE_MENU) {
mReactInstanceManager.showDevOptionsDialog();
getReactNativeHost().getReactInstanceManager().showDevOptionsDialog();
return true;
}
if (mDoubleTapReloadRecognizer.didDoubleTapR(keyCode, getCurrentFocus())) {
mReactInstanceManager.getDevSupportManager().handleReloadJS();
getReactNativeHost().getReactInstanceManager().getDevSupportManager().handleReloadJS();
}
}
return super.onKeyUp(keyCode, event);
}

@Override
public void onBackPressed() {
if (mReactInstanceManager != null) {
mReactInstanceManager.onBackPressed();
if (getReactNativeHost().hasInstance()) {
getReactNativeHost().getReactInstanceManager().onBackPressed();
} else {
super.onBackPressed();
}
Expand All @@ -229,8 +179,8 @@ public void invokeDefaultOnBackPressed() {

@Override
public void onNewIntent(Intent intent) {
if (mReactInstanceManager != null) {
mReactInstanceManager.onNewIntent(intent);
if (getReactNativeHost().hasInstance()) {
getReactNativeHost().getReactInstanceManager().onNewIntent(intent);
} else {
super.onNewIntent(intent);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/

package com.facebook.react;

public interface ReactApplication {

/**
* Get the default {@link ReactNativeHost} for this app.
*/
ReactNativeHost getReactNativeHost();
}
125 changes: 125 additions & 0 deletions ReactAndroid/src/main/java/com/facebook/react/ReactNativeHost.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/

package com.facebook.react;

import javax.annotation.Nullable;

import java.util.List;

import android.app.Application;

import com.facebook.infer.annotation.Assertions;

/**
* Simple class that holds an instance of {@link ReactInstanceManager}. This can be used in your
* {@link Application class} (see {@link ReactApplication}), or as a static field.
*/
public abstract class ReactNativeHost {

private final Application mApplication;
private @Nullable ReactInstanceManager mReactInstanceManager;

protected ReactNativeHost(Application application) {
mApplication = application;
}

/**
* Get the current {@link ReactInstanceManager} instance, or create one.
*/
public ReactInstanceManager getReactInstanceManager() {
if (mReactInstanceManager == null) {
mReactInstanceManager = createReactInstanceManager();
}
return mReactInstanceManager;
}

/**
* Get whether this holder contains a {@link ReactInstanceManager} instance, or not. I.e. if
* {@link #getReactInstanceManager()} has been called at least once since this object was created
* or {@link #clear()} was called.
*/
public boolean hasInstance() {
return mReactInstanceManager != null;
}

/**
* Destroy the current instance and release the internal reference to it, allowing it to be GCed.
*/
public void clear() {
if (mReactInstanceManager != null) {
mReactInstanceManager.destroy();
mReactInstanceManager = null;
}
}

protected ReactInstanceManager createReactInstanceManager() {
ReactInstanceManager.Builder builder = ReactInstanceManager.builder()
.setApplication(mApplication)
.setJSMainModuleName(getJSMainModuleName())
.setUseDeveloperSupport(getUseDeveloperSupport())
.setInitialLifecycleState(LifecycleState.BEFORE_CREATE);

for (ReactPackage reactPackage : getPackages()) {
builder.addPackage(reactPackage);
}

String jsBundleFile = getJSBundleFile();
if (jsBundleFile != null) {
builder.setJSBundleFile(jsBundleFile);
} else {
builder.setBundleAssetName(Assertions.assertNotNull(getBundleAssetName()));
}

return builder.build();
}

/**
* Returns the name of the main module. Determines the URL used to fetch the JS bundle
* from the packager server. It is only used when dev support is enabled.
* This is the first file to be executed once the {@link ReactInstanceManager} is created.
* e.g. "index.android"
*/
protected String getJSMainModuleName() {
return "index.android";
}

/**
* Returns a custom path of the bundle file. This is used in cases the bundle should be loaded
* from a custom path. By default it is loaded from Android assets, from a path specified
* by {@link getBundleAssetName}.
* e.g. "file://sdcard/myapp_cache/index.android.bundle"
*/
protected @Nullable String getJSBundleFile() {
return null;
}

/**
* Returns the name of the bundle in assets. If this is null, and no file path is specified for
* the bundle, the app will only work with {@code getUseDeveloperSupport} enabled and will
* always try to load the JS bundle from the packager server.
* e.g. "index.android.bundle"
*/
protected @Nullable String getBundleAssetName() {
return "index.android.bundle";
}

/**
* Returns whether dev mode should be enabled. This enables e.g. the dev menu.
*/
protected abstract boolean getUseDeveloperSupport();

/**
* Returns a list of {@link ReactPackage} used by the app.
* You'll most likely want to return at least the {@code MainReactPackage}.
* If your app uses additional views or modules besides the default ones,
* you'll want to include more packages here.
*/
protected abstract List<ReactPackage> getPackages();
}
25 changes: 0 additions & 25 deletions local-cli/generator-android/templates/package/MainActivity.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
package <%= package %>;

import com.facebook.react.ReactActivity;
import com.facebook.react.ReactPackage;
import com.facebook.react.shell.MainReactPackage;

import java.util.Arrays;
import java.util.List;

public class MainActivity extends ReactActivity {

Expand All @@ -17,24 +12,4 @@ public class MainActivity extends ReactActivity {
protected String getMainComponentName() {
return "<%= name %>";
}

/**
* Returns whether dev mode should be enabled.
* This enables e.g. the dev menu.
*/
@Override
protected boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}

/**
* A list of packages used by the app. If the app uses additional views
* or modules besides the default ones, add more packages here.
*/
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage()
);
}
}
Loading

42 comments on commit 49f20f4

@foghina
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@grabbou heads-up: this is a breaking change. I had to change the template to add some new functionality that will be used for background JS. New apps will not have any issues, but upgrading might be difficult. In theory (not tested) old apps should continue to work without updating the template, but react-native upgrade might be problematic.

Let me know if you need more info!

@grabbou
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Just wanted to say this is a good way for providing extra explanation and informing about breaking changes. I am travelling right now, but saved that and will answer tomorrow :)

CC: @mkonicek @bestander

@baoti
Copy link

@baoti baoti commented on 49f20f4 Jun 17, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@foghina Examples (Movies and UIExplorer) are broken.

@leeight
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@orhan
Copy link

@orhan orhan commented on 49f20f4 Jun 21, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As @chirag04 already pointed out, there is no easy way to access a reference to the MainActivity anymore when calling getPackages, so this breaks a lot of third-party modules which require an activity to be passed in their constructor.

I haven't fully understood why we are going to the route of using MainApplication for creating a ReactInstanceManager, so I can't come up with a proper solution to this.

Is there any way to handle this gracefully?

@baoti
Copy link

@baoti baoti commented on 49f20f4 Jun 22, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chirag04 @orhan When public the ReactContext.getCurrentActivity, you can use it from third-party modules.

@foghina
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@orhan that is by design. We persist React instances across Activities, so packages & modules shouldn't take activities in constructors and should never hold long-lived references to them, as that will cause leaks. Instead, modules can extend ReactContextBaseJavaModule and use getCurrentActivity() when they need it. This may make it harder to integrate 3rd party libraries, but it enforces certain assumptions that let us decouple the React instance from the Activity.

@nihgwu
Copy link
Contributor

@nihgwu nihgwu commented on 49f20f4 Jun 24, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i've upgrade to 0.29, then how can i get those third-party packages to work witch need to pass the Activity to the constructor such as react-native-code-push

@satya164
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nihgwu Those packages need to be updated so that they don't need the activity to be passed in the constructor. The correct way is to extend ReactContextBaseJavaModule and use the getCurrentActivity() method.

@lostintangent
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nihgwu We're making the necessary changes to the CodePush plugin right now in order to support RN 0.29. We should have a release out by early next week.

@nihgwu
Copy link
Contributor

@nihgwu nihgwu commented on 49f20f4 Jun 24, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lostintangent that's great, then i'll disabled it temporarily

@kraffslol
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would something like onActivityResult be used now?

@foghina
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kraffslol same way as before, just need to go through the host.

@kraffslol
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@foghina Could you give an example on how this is done? Placing the onActivityResult method inside of the ReactNativeHost constructor in MainApplication.java results in a compilation error. When placing it in MainActivity it compiles, but from what I can see there's no way for me to pass the data on to my module.

@satya164
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kraffslol
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@satya164 Thank you!

@nihgwu
Copy link
Contributor

@nihgwu nihgwu commented on 49f20f4 Jun 29, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm using the react-native-splashscreen package in my app, and I've tried to using the getCurrentActivity method, but it's always null, I don't know why because I know nothing about native android development, I've also checked the new version of react-native-code-push which already add the support for RN0.29, but find no clue.
https://github.com/remobile/react-native-splashscreen/blob/master/android/src/main/java/com/remobile/splashscreen/RCTSplashScreen.java#L83

@satya164
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nihgwu It's set in onResume, so it'll be null when you try to access it inside constructor.

@nihgwu
Copy link
Contributor

@nihgwu nihgwu commented on 49f20f4 Jun 29, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@satya164 Thank you so much, sorry to bother you but how can I achieve it in the new structure, now i move the SplashScreen.show to the index.js and it works but there is still white screen between the launch and RN initialization

@satya164
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nihgwu I think your module can subscribe to lifecycle events - http://facebook.github.io/react-native/docs/native-modules-android.html#listening-to-lifecycle-event and then you can call the function in onHostResume

@nihgwu
Copy link
Contributor

@nihgwu nihgwu commented on 49f20f4 Jun 29, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@satya164 It works, thanks

@avishayil
Copy link

@avishayil avishayil commented on 49f20f4 Jul 1, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @satya164, i'm trying to get the activity from the module but it gets null:

public class MyPackage extends ReactContextBaseJavaModule implements LifecycleEventListener {

    private ReactContext mReactContext;

    public MyPackage(ReactApplicationContext reactContext) {
        super(reactContext);

        mReactContext = reactContext;
        Log.d("ACTIVITY MYPACKAGE", this.getCurrentActivity().getLocalClassName());   
   }
}

Log produces Attempted to invoke virtual method ... getLocalClassName() on a null object reference.

If i'm moving the code to the onHostResume, it never happens.

Any ideas?

@satya164
Copy link
Contributor

@satya164 satya164 commented on 49f20f4 Jul 1, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@avishayil as mentioned earlier, you can't use getCurrentActivity in the module constructor. Also seems you're not adding the listener itself, that's why onHostResume never gets called. Check the docs on how to add listener - http://facebook.github.io/react-native/docs/native-modules-android.html#listening-to-lifecycle-event

@nihgwu
Copy link
Contributor

@nihgwu nihgwu commented on 49f20f4 Jul 1, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@avishayil #8509 (comment) you can check the diff I've pasted there from the original one, I'm sure you will find the solution

@avishayil
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nailed it, thanks!

@varungupta85
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the motivation behind this change? I wanted to have some context before I start to make the suggested changes in my application. I don't have any experience in native android development. So, I am sorry if it is obvious. Thanks!

@marcshilling
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@foghina @satya164 have you put any thought into how Native UI Components can access the activity? It's easy for Native Modules to extend ReactContextBaseJavaModule, but Native UI Components are already extending SimpleViewManager, so I'm not sure how to proceed...

@foghina
Copy link
Contributor Author

@foghina foghina commented on 49f20f4 Jul 8, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@marcshilling view managers shouldn't be tied to activities either. Why do you need to access the Activity from a view manager? In methods that take a view as a parameter (such as prop setters), you may be able to cast View#getContext() to an Activity, though I'm not 100% sure. Check out the answer here as well:

http://stackoverflow.com/questions/8276634/android-get-hosting-activity-from-a-view

@marcshilling
Copy link

@marcshilling marcshilling commented on 49f20f4 Jul 8, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@foghina I'm bridging an Android library that vends a Fragment. In order to add a fragment to a view you need a FragmentManager, and in order to get a FragmentManager you need an Activity. This was my SimpleViewManager's constructor prior to 0.29:

    public LSPlayerManager(Activity activity) {
        mActivity = activity;
        mFragmentManager = mActivity.getFragmentManager();
    }

Then later on, I:

        mPlayerFragment = VRLSPlayerFragment.newInstance();
        mFragmentManager.beginTransaction().add(mPlayerFragment, mPlayerFragment.FRAGMENT_TAG).commit();
        mFragmentManager.executePendingTransactions();
        mFrameLayout.addView(mPlayerFragment.getView());

FWIW, I tried to pass reactContext into the constructor and cast it to activity, but this crashes:
mActivity = (Activity)((Context)reactContext);

@foghina
Copy link
Contributor Author

@foghina foghina commented on 49f20f4 Jul 8, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, ReactContext is not an instance of Activity. But the context that we pass to view managers from the UIManager is different, so check the context passed to createViewInstance, it might be an instance of Activity.

If not, feel free to send a PR that adds getCurrentActivity() to view managers :).

@marcshilling
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't even think it would be possible to add getCurrentActivity() to SimpleViewManager because those have no context. It seems like we'd have to completely reverse course on the decisions that 0.29.0 made (moving things in MainApplication instead of MainActivity).

@foghina
Copy link
Contributor Author

@foghina foghina commented on 49f20f4 Jul 8, 2016 via email

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stan229
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another use case where Activity is necessary for a class that extends SimpleViewManager is when rendering YouTube videos using the Android YouTube API. To instantiate a YouTubePlayerView you need to pass the activity. I'm also curious if there are any short-term workarounds for this that we can implement, as I do not really want to regress versions.

@marcshilling
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stan229 I've yet to find a workaround. As far as I can tell this change is a regression that needs to be addressed. I've opened an issue here: #8661

@darmie
Copy link

@darmie darmie commented on 49f20f4 Jul 17, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@satya164 I have the latest version of react-native 0.29.1 , and I am trying to update a native module.

Gradle cannot find com.facebook.react.bridge.ActivityEventListener; and getCurrentActivity();

node_modules\react-native-android-statusbar\src\main\java\me\neo\react\StatusBarModule.java:15: error: cannot find symbol
import com.facebook.react.bridge.ActivityEventListener;

                                ^
  symbol:   class ActivityEventListener
  location: package com.facebook.react.bridge
C:\Users\Damilare\myapp\node_modules\react-native-android-statusbar\src\main\java\me\neo\react\StatusBarModule.java:55: error: cannot find symbol
        Activity mActivity = this.getCurrentActivity();
                                 ^
  symbol: method getCurrentActivity()
C:\Users\Damilare\myapp\node_modules\react-native-android-statusbar\src\main\java\me\neo\react\StatusBarModule.java:60: error: local variable mActivity is accessed from within inner class; needs to be declared final
                    Window window = mActivity.getWindow();
                                    ^
C:\Users\Damilare\myapp\node_modules\react-native-android-statusbar\src\main\java\me\neo\react\StatusBarModule.java:74: error: cannot find symbol
        Activity mActivity = this.getCurrentActivity();
                                 ^
  symbol: method getCurrentActivity()
C:\Users\Damilare\myapp\node_modules\react-native-android-statusbar\src\main\java\me\neo\react\StatusBarModule.java:78: error: local variable mActivity is accessed from within inner class; needs to be declared final
                View decorView = mActivity.getWindow().getDecorView();
                                 ^
C:\Users\Damilare\trybal\node_modules\react-native-android-statusbar\src\main\java\me\neo\react\StatusBarModule.java:88: error: cannot find symbol
        Activity mActivity = this.getCurrentActivity();
                                 ^
  symbol: method getCurrentActivity()
C:\Users\Damilare\myapp\node_modules\react-native-android-statusbar\src\main\java\me\neo\react\StatusBarModule.java:94: error: local variable mActivity is accessed from within inner class; needs to be declared final
                View decorView = mActivity.getWindow().getDecorView();
                                 ^
7 errors
:react-native-android-statusbar:compileReleaseJavaWithJavac FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':react-native-android-statusbar:compileReleaseJavaWithJavac'.
> Compilation failed; see the compiler error output for details.

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.

BUILD FAILED

Total time: 21.683 secs
Could not install the app on the device, read the error above for details.
Make sure you have an Android emulator running or a device connected and have
set up your Android development environment:
https://facebook.github.io/react-native/docs/android-setup.html

This is the source:

package me.neo.react;

import android.app.Activity;
import android.graphics.Color;
import android.os.Build;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;

import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ActivityEventListener;
import com.facebook.react.bridge.ReactContext;


/**
 * Created by Nishanth Shankar on 9/24/15.
 */
public class StatusBarModule extends ReactContextBaseJavaModule{

    public StatusBarModule(ReactApplicationContext reactContext) {
        super(reactContext);
        //reactContext.addActivityEventListener(this);
        //mActivity = 
        //mActivity = activity;
    }

    @Override
    public String getName() {
        return "StatusBarAndroid";
    }

    @ReactMethod
    public void setHexColor(String hex){
        int color = Color.parseColor(hex);
        setStatusColor(color);
    }

    @ReactMethod
    public void setRGB(int r, int g, int b){
        int color = Color.rgb(r,g,b);
        setStatusColor(color);
    }

    @ReactMethod
    public void setARGB(int a,int r, int g, int b){
        int color = Color.argb(a, r, g, b);
        setStatusColor(color);
    }

    void setStatusColor(final int color){
        Activity mActivity = this.getCurrentActivity();
        mActivity.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                if(Build.VERSION.SDK_INT >= 21){
                    Window window = mActivity.getWindow();
                    window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
                    window.setStatusBarColor(color);

                }

            }
        });

    }


    @ReactMethod
    public void hideStatusBar(){
        Activity mActivity = this.getCurrentActivity();
        mActivity.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                View decorView = mActivity.getWindow().getDecorView();
                // Hide the status bar.
                int uiOptions = View.SYSTEM_UI_FLAG_FULLSCREEN;
                decorView.setSystemUiVisibility(uiOptions);
            }
        });
    }

    @ReactMethod
    public void showStatusBar(){
        Activity mActivity = this.getCurrentActivity();
        mActivity.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                if(Build.VERSION.SDK_INT < 16)
                    return;
                View decorView = mActivity.getWindow().getDecorView();
                // Hide the status bar.
                int uiOptions = View.SYSTEM_UI_FLAG_VISIBLE;
                decorView.setSystemUiVisibility(uiOptions);
            }
        });
    }


}

@satya164
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have a sample project somewhere?

@darmie
Copy link

@darmie darmie commented on 49f20f4 Jul 18, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@satya164 Nevermind, I fixed it by reinstalling react-native. I don't know how or why, but it worked.

@minhnguyen-paidy
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi guys, this is an old issue but I'm wondering if the problem still exists today.
Basically I have FaceVerificationViewManager that extends SimpleViewManager. I want to call a method from an external SDK which requires the current activity on init of the view.
Is reactContext.currentActivity going to be null in init by any chance?

class FaceVerificationView(context: Context, reactContext: ReactApplicationContext): ReactTextView(context) {
    private val mCallerContext: ReactApplicationContext = reactContext

    private val mActivityEventListener: ActivityEventListener =
        object : BaseActivityEventListener() {
            override fun onActivityResult() {
                // some logic for onCompletion
            }
        }

    init {
        reactContext.addActivityEventListener(mActivityEventListener)
        reactContext.currentActivity?.let {
            SDK.getInstance(mCallerContext).verifyFace(it)
        }
    }
}

class FaceVerificationViewManager(private val mCallerContext: ReactApplicationContext) :
    SimpleViewManager<ReactTextView>() {
    override fun getName(): String {
        return "FaceVerification"
    }

    override fun getExportedCustomDirectEventTypeConstants(): MutableMap<String, Any> =
        mutableMapOf("onCompletion" to mutableMapOf("registrationName" to "onCompletion"))

    override fun createViewInstance(context: ThemedReactContext): ReactTextView {
        return FaceVerificationView(context, reactContext = mCallerContext)
    }
}

@jimdog1001
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi guys, this is an old issue but I'm wondering if the problem still exists today. Basically I have FaceVerificationViewManager that extends SimpleViewManager. I want to call a method from an external SDK which requires the current activity on init of the view. Is reactContext.currentActivity going to be null in init by any chance?

class FaceVerificationView(context: Context, reactContext: ReactApplicationContext): ReactTextView(context) {
    private val mCallerContext: ReactApplicationContext = reactContext

    private val mActivityEventListener: ActivityEventListener =
        object : BaseActivityEventListener() {
            override fun onActivityResult() {
                // some logic for onCompletion
            }
        }

    init {
        reactContext.addActivityEventListener(mActivityEventListener)
        reactContext.currentActivity?.let {
            SDK.getInstance(mCallerContext).verifyFace(it)
        }
    }
}

class FaceVerificationViewManager(private val mCallerContext: ReactApplicationContext) :
    SimpleViewManager<ReactTextView>() {
    override fun getName(): String {
        return "FaceVerification"
    }

    override fun getExportedCustomDirectEventTypeConstants(): MutableMap<String, Any> =
        mutableMapOf("onCompletion" to mutableMapOf("registrationName" to "onCompletion"))

    override fun createViewInstance(context: ThemedReactContext): ReactTextView {
        return FaceVerificationView(context, reactContext = mCallerContext)
    }
}

hey did you figure this out? i to get the current activity from the constructor but it returns null. is it possible get it somehow?

@minhnguyen-paidy
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jimdog1001 not yet. I asked for help by sending emails to React Native contributors but no one replies :(

@RubenPauwelsUniWeb
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@minhnguyen-paidy Did you find a solution in the end?
We are stuck on the same problem. We are also implementing an external SDK and need to have the current activity for this.

@juniorforlife
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@RubenPauwelsUniWeb Sorry, I left the project almost 2 years ago, and now it has become someone else's problem. 😅

Please sign in to comment.