Home » Android » How to access Activity from a React Native Android module?

How to access Activity from a React Native Android module?

Posted by: admin April 23, 2020 Leave a comment

Questions:

I’m attempting to bridge over the Android functionality of keeping the screen on to React Native. I figured I could do this with a simple module, however I don’t know how to get access to the current Android Activity from said module.

I need the Activity reference so I can call .getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); on it

I tried to get the activity via casting like so ((Activity)getReactApplicationContext().getBaseContext()), but this throws a “cannot be cast to Android.app.Activity” error

How to&Answers:

CustomReactPackage.java:

public class CustomReactPackage implements ReactPackage {

    private Activity mActivity = null;

    public CustomReactPackage(Activity activity) {
        mActivity = activity;
    }

    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
        List<NativeModule> modules = new ArrayList<>();
        // Add native modules here
        return modules;
    }

    public List<Class<? extends JavaScriptModule>> createJSModules() {
        return Collections.emptyList();
    }

    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        List<ViewManager> modules = new ArrayList<>();
        // Add native UI components here
        modules.add(new LSPlayerManager(mActivity));
        return modules;
    }
}

LSPlayerManager is my native UI component. I define a constructor so that I can pass in the activity:

public LSPlayerManager(Activity activity) {
    mActivity = activity;
}

And finally in MainActivity.java where the ReactInstanceManager is defined, we can pass the activity to our custom React package:

mReactInstanceManager = ReactInstanceManager.builder()
        .setApplication(getApplication())
        .setBundleAssetName("index.android.bundle")
        .setJSMainModuleName("src/index.android")
        .addPackage(new MainReactPackage())
        .addPackage(new CustomReactPackage(this)) // <--- LIKE THIS!
        .setUseDeveloperSupport(BuildConfig.DEBUG)
        .setInitialLifecycleState(LifecycleState.RESUMED)
        .build();

UPDATE FOR REACT NATIVE 0.29.0

This is no longer how you access activity in a native module. See https://github.com/facebook/react-native/releases/tag/v0.29.0 for migration instructions

Answer:

I guess getCurrentActivity() method of ReactContextBaseJavaModule might be used like the following code which has been copied from React-Native original code;

import android.app.Activity;
import com.facebook.react.bridge.ReactContextBaseJavaModule;

public class AwesomeModule extends ReactContextBaseJavaModule {

  public AwesomeModule(ReactApplicationContext reactContext) {
    super(reactContext);
  }

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

  private static final String ERROR_NO_ACTIVITY = "E_NO_ACTIVITY";
  private static final String ERROR_NO_ACTIVITY_MESSAGE = "Tried to do the something while not attached to an Activity";

  @ReactMethod
  public void doSomething(successCallback, errorCallback) {

    final Activity activity = getCurrentActivity();

    if (activity == null) {
      errorCallback(ERROR_NO_ACTIVITY, ERROR_NO_ACTIVITY_MESSAGE);
      return;
    }

  }

}

Answer:

Editted:

The issue is that getReactApplicationContext() returns the context of the Application and not the Activity. You cannot typecast an Application context to an Activity.

This is a simple workaround

Since usually, there is only one activity (Main Activity) in react-native, We can write a static function in MainActivity to return the activity

private static Activity mCurrentActivity = null;

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mCurrentActivity = this;
    ...
}
...
public static Activity getActivity(){
    Activity activity = new Activity();
    activity = mCurrentActivity;
    return activity;
}

then call MainActivity.getActivity() from the bridge modules

Answer:

For 0.28 and before, you can get activity from ReactInstanceManagerImpl ‘s private @Nullable Activity mCurrentActivity;, 0.29+ ‘s ReactContextBaseJavaModule using the same field.

Answer:

How do you pass the activity to a package from ReactApplication::getPackages() ?

I do not understand. I cannot find any clear examples

package com.bluetoothioexample;

import android.app.Application;
import android.util.Log;

import com.facebook.react.bridge.ReactContextBaseJavaModule;

import com.facebook.react.ReactApplication;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.react.shell.MainReactPackage;

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

import com.subsite.bluetoothio.BluetoothIOPackage;
import com.oblador.vectoricons.VectorIconsPackage;

public class MainApplication extends Application implements ReactApplication {

  private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
    @Override
    protected boolean getUseDeveloperSupport() {
      return BuildConfig.DEBUG;
    }

    @Override
    protected List<ReactPackage> getPackages() {
      return Arrays.<ReactPackage>asList(
          new MainReactPackage(),
          new BluetoothIOPackage(this), //<- How pass the activity
                                        // from ReactApplication ?
          new VectorIconsPackage()
      );
    }
  };

  @Override
  public ReactNativeHost getReactNativeHost() {
      return mReactNativeHost;
  }
}

Answer: You do NOT pass Activity from MainApplication to the package.

You call getCurrentActivity() from within your ReactContextBaseJavaModule

Answer:

It’s not super clear from https://github.com/facebook/react-native/blob/master/docs/NativeModulesAndroid.md, but you can read the source code of the ToastModule in the react native distribution to see how they do it github.com/facebook/react-native/blob/daba14264c9f0d29c0327440df25d81c2f58316c/ReactAndroid/src/main/java/com/facebook/react/modules/toast/ToastModule.java#L31-L33

The idea is that the main activity will be passed as the reactContext in the initializer for the module called from ReactInstanceManager.builder() call in github.com/facebook/react-native/blob/3b4845f93c296ed93c348733809845d587227823/local-cli/generator-android/templates/package/MainActivity.java#L28 via the instantiation of MainReactPackage github.com/facebook/react-native/blob/daba14264c9f0d29c0327440df25d81c2f58316c/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java#L49

Answer:

I needed to modify an outdated module (react-android-360-video) and found this helpful…

In android/app/src/main/java/com/webcdpmobiledemo/MainApplication.java, I used the new format for adding a package:

...
import com.vrvideocomponent.VrVideoViewPackage;

public class MainApplication extends Application implements ReactApplication {

    ...

    private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
      ...

      @Override
      protected List<ReactPackage> getPackages() {
        return Arrays.<ReactPackage>asList(
            new MainReactPackage(),
            new VrVideoViewPackage()
        );
      }

      ...
    };

    ...
}

And android/app/src/main/java/com/webcdpmobiledemo/MainActivity.java is essentially empty:

package com.yourproject;

import com.facebook.react.ReactActivity;

public class MainActivity extends ReactActivity {

    /**
     * Returns the name of the main component registered from JavaScript.
     * This is used to schedule rendering of the component.
     */
    @Override
    protected String getMainComponentName() {
        return "YourProject";
    }
}

Then, I modified the VrVideoViewPackage file, which needs to pass the reactContext to the VrVideoViewManager it calls:

...

public class VrVideoViewPackage implements ReactPackage {
    ...

    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        return Arrays.<ViewManager>asList(
                new VrVideoViewManager(reactContext)
        );
    }

}

And finally, in the VrVideoViewManager the activity can be accessed like so:

...

import android.app.Activity;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.bridge.ReactContext;

...

public class VrVideoViewManager extends SimpleViewManager<VrVideoView> {
    ...

    public VrVideoViewManager(ReactContext reactContext) {
        // Do not store mActivity, always getCurrentActivity when needed
        Activity mActivity = mContext.getCurrentActivity();
    }

    @Override
    protected VrVideoView createViewInstance(ThemedReactContext reactContext) {

        // You can also activity from ThemedReactContext
        Activity mActivity = reactContext.getCurrentActivity();

        VrVideoView vrView = new VrVideoView(mActivity);
        vrView.setEventListener(new ActivityEventListener(vrView));
        vrView.pauseVideo();
        return new VrVideoView(mActivity);
    }

    ...
}