marcshilling
marcshilling

Reputation: 1445

How to access Activity from a React Native Android module?

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

Upvotes: 45

Views: 79780

Answers (11)

Budi
Budi

Reputation: 1

my code like this final AlertDialog.Builder builder = new AlertDialog.Builder(view.getContext()); and not found error for running

Upvotes: 0

Mahdi-Malv
Mahdi-Malv

Reputation: 19220

If you want to get current instance of the foreground activity in a ReactContextBaseJavaModule subclass, use:

// class MyReactModule extends ReactContextBaseJavaModule
Activity activity = getCurrentActivity();

It comes from ReactContextBaseJavaModule#getCurrentActivity which uses reactContext.getCurrentActivity()

Note: ReactApplicationContext#getCurrentActivity is package private. So you can not use reactContext.getCurrentActivity().

  • ReactContextBaseJavaModule's is protected. So deriving it makes it achievable:
protected @Nullable final Activity getCurrentActivity() {
    return mReactApplicationContext.getCurrentActivity();
  }
  • ReactApplicationContext's is package private (default access). So you can't get it:
/* package */ @Nullable Activity getCurrentActivity() {
    return mCurrentActivity;
  }

Bunos

Since it's @Nullable, make sure you check it's availability:

if (reactContext.hasCurrentActivity()) {
   // There is. So get it
   Activity theActivity = getCurrentActivity();
}

Upvotes: 2

chengsam
chengsam

Reputation: 7405

For custom UI component, you can get the context in the constructor, and later you can get the activity by context.getCurrentActivity()

Upvotes: 1

King Friday
King Friday

Reputation: 26076

Get Activity / Window / Decor View with UI Thread context

In your class file imports include...

import android.view.WindowManager;
import android.view.Window;
import android.view.View;

In your class constructor...

private static ReactApplicationContext reactContext;
MyModule(ReactApplicationContext context) {
  super(context);
  reactContext = context;
}

Then in for class methods...

@ReactMethod
public void doSomethingRequiringWindow() {
  // get access to current UI thread first
  reactContext.getCurrentActivity().runOnUiThread(new Runnable() {
    @Override
    public void run() {
      Window window = reactContext
       .getCurrentActivity().getWindow();
      View decorView = reactContext
       .getCurrentActivity().getWindow().getDecorView();
      // you have access to main ui thread so you can effect
      // immediate behavior on window and decorView
    }
  });
}

Upvotes: 2

David Sinclair
David Sinclair

Reputation: 4417

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);
    }

    ...
}

Upvotes: -1

Ed of the Mountain
Ed of the Mountain

Reputation: 5459

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

Upvotes: 0

oscarthecat
oscarthecat

Reputation: 1841

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

Upvotes: 0

efkan
efkan

Reputation: 13217

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;
    }

  }

}

Upvotes: 42

marcshilling
marcshilling

Reputation: 1445

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

Upvotes: 43

Nishanth Shankar
Nishanth Shankar

Reputation: 1976

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

Upvotes: 9

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

Upvotes: -1

Related Questions