facebook / react-native

A framework for building native applications using React
https://reactnative.dev
MIT License
118.08k stars 24.19k forks source link

Android Native Module: Get ReactContext to call sendEvent method from another class #5846

Closed AntiHate closed 8 years ago

AntiHate commented 8 years ago

I know I should't be creating a issue for this but I've already asked this at stackoverflow, didn't got any answer yet.

I'm trying to add android native module to send events to JS. I'm using a library which trigger events in native class. I need to send those events back to JS.

MyCustomModule.java

public class MyCustomModule extends ReactContextBaseJavaModule {

  private ReactContext mReactContext;

  public MyCustomModule(ReactApplicationContext reactContext) {
    super(reactContext);
    mReactContext = reactContext;
  }

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

    private void sendEvent(String eventName, Object params) {
        mReactContext
            .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
            .emit(eventName, params);
    }    
}

OtherClass.java

public class OtherClass extends AnotherClass {

    @Override
    protected void eventOccurred(Context context) {
        MyCustomModule RNC = new MyCustomModule(); // -> Can't initialise it without reactContext
        RNC.sendEvent("CustomEvent", objectData); //-> Can't call
    }   
}

Can I get access to ReactContext in OtherClass? or any other way to accomplish this? Any help would be much appreciated.

facebook-github-bot commented 8 years ago

Hey antihate, thanks for reporting this issue!

React Native, as you've probably heard, is getting really popular and truth is we're getting a bit overwhelmed by the activity surrounding it. There are just too many issues for us to manage properly.

kocyigityunus commented 8 years ago

you can get ReactContext from view's getContext() method.

also you are extending wrong classes. please search for ReactSwitch.java, ReactSwitchEvent.java and ReactSwitchManager.java files.

AntiHate commented 8 years ago

Thanks @kocyigityunus for the solution, I was doing it all wrong that's because I've no idea about the android native code.

I've solved my problem by using LocalBroadcastManager to send events to NativeModule.

pppluto commented 8 years ago

hey,i am working for this alos.can you tell me how to use localBroadcastManager? i want native send event to js and also cant initialise without reactContext

iwenchao commented 7 years ago

yeap,i am facing the same question. please give me a answer, thanks a lot

Thibaut-Fatus commented 7 years ago

Me too, the docs on events is quite cryptic since it assumes you have a ReactContext on hand, but a lot of people seems to have a very hard time sending events from native android to js

iwenchao commented 7 years ago

last month when i read reactnative source code , i found there was a way to acheve ReactContext.sentEvent; Maybe you should change the way your code had been written.

hnvcam commented 7 years ago

I post the work around here using LocalBroadcastManager as AntiHate suggested, for those still couldn't find it. The ideal is to have an inner class to listen for events from LocalBroadcastManager.

  1. First we need a native module as usual:

    public class SomeNativeModule extends ReactContextBaseJavaModule {
    
    private ReactContext mReactContext;
    // instance of our receiver
    private LocalBroadcastReceiver  mLocalBroadcastReceiver;
    
    public SomeNativeModule(ReactApplicationContext reactContext) {
        super(reactContext);
        this.mReactContext = reactContext;
        this.mLocalBroadcastReceiver = new LocalBroadcastReceiver();
        LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(reactContext);
        localBroadcastManager.registerReceiver(mLocalBroadcastReceiver, new IntentFilter("my-custom-event"));
    }
  2. Add inner class (of SomeNativeModule) for receive the custom local broadcast:

    
    public class LocalBroadcastReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
               String someData = intent.getStringExtra("my-extra-data");
               mReactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                      .emit("JS-Event", someData);
         }
    }
    }
3. In your other android class, fire the custom local broadcast event:

public class SomeService extends IntentService { @Override protected void onHandleIntent(Intent intent) { LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(this); Intent customEvent= new Intent("my-custom-event"); customEvent.putExtra("my-extra-data", "that's it"); localBroadcastManager.sendBroadcast(customEvent); } }

sergchil commented 7 years ago

@hnvcam how unregister LocalBroadcastManager ?

kamaljit-sharma commented 7 years ago

@hhvm-bot thank you very much, your solution worked for me 👍

HappyToper commented 6 years ago

@hnvcam worked for me too, thank you !!!

wdifruscio commented 6 years ago

Hi There,

I have tried to implement @hnvcam solution to detect when android location services(GPS) are turned on and off. My application requires that if it is turned off the app needs to go to a different screen. The easiest way to do this instead of polling if Location is on or off is using a broadcast. I copied the code almost exactly, just modifying it with my own event parameters.

The event fires properly and the intended behaviour is perfect! Unfortunately, the second time it is fired I get this in logcat.

W/unknown:React: Calling JS function after bridge has been destroyed.

My javascript implementation is simply

    if (Platform.OS === "android") {
      DeviceEventEmitter.addListener("gpsChanged", e => {
        this.checkLocationEnabled();
      });
    }

Any thoughts would be appreciated 😭

hnvcam commented 6 years ago

@willcodes I think probably you forget to remove that listener when componentWillUnmount and that leads to the case that your previous instance of that screen still wired to "gpsChanged" event.

wdifruscio commented 6 years ago

@hnvcam Yep, that was the issue. Figured it out a little after I posted. Thanks for your reply/solution though, was very helpful

mkouchi commented 6 years ago

Also worked for me. Thanks. but how unregister localBroadcastReceiver?

wordyallen commented 6 years ago

This is how I registered and unregistered, if anyone's interested:

class AlarmModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {

    override fun getName(): String {
        return "AlarmModule"
    }

    private val alarmReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent?) {
            Toast.makeText(reactContext, "called from test receiver", Toast.LENGTH_SHORT).show()
        }
    }

    @ReactMethod
    fun setAlarm(message: String, hour: Int, minute:Int) {

        reactApplicationContext.registerReceiver(alarmReceiver, IntentFilter("my-custom-event"))
        val alarmIntent = Intent("my-custom-event")
        val piAlarm = PendingIntent.getBroadcast(reactApplicationContext, 0, alarmIntent, PendingIntent.FLAG_UPDATE_CURRENT)
        val am = reactApplicationContext!!.getSystemService(ReactContext.ALARM_SERVICE) as AlarmManager
        am.set(AlarmManager.RTC_WAKEUP, 0, piAlarm)

    }

    @ReactMethod
    fun cancelAlarm(){
        reactApplicationContext.unregisterReceiver(alarmReceiver)
    }

}
abs-cbn-iptv-xcms commented 6 years ago

Why not just use

public static ReactContext mReactContext; //?

Unless this can be a security issue, I guess this is enough.

mickamy commented 6 years ago

@eugenecp Holding Context as a static property is a bad pettern in Android, for it can cause memory leaks. See https://android-developers.googleblog.com/2009/01/avoiding-memory-leaks.html for more details.

theebi commented 6 years ago

I have an android service running in the background which gets location updates periodically. And Im passing the data back to the ReactContextBaseJavaModule through Broadcast Receiver. This doesn't work if the app goes to background (Service is still running). Any idea how do I pass the data ?

mickamy commented 6 years ago

@theebi I am not sure, but maybe Headless JS would help you I guess https://facebook.github.io/react-native/docs/headless-js-android.html

thg303 commented 6 years ago

here's how I did it in an Service extended class (it will run when the intent defined in android manifest file arrives). may help somebody.

public class MyPushListener extends Service {

  @Override
  public void onMessageReceived(JSONObject message, JSONObject content){
    MainApplication application = (MainApplication) this.getApplication();

    ReactNativeHost reactNativeHost = application.getReactNativeHost();
    ReactInstanceManager reactInstanceManager = reactNativeHost.getReactInstanceManager();
    ReactContext reactContext = reactInstanceManager.getCurrentReactContext();

    if (reactContext != null) {
      WritableNativeArray params = new WritableNativeArray();
      params.pushString(message.toString());
      reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
          .emit("EVENT_HAS_TRIGGERED", params);
    }
  }
}
theebi commented 6 years ago

Hai @thg303 , I have written the module as a separate node module any idea how can I access MainApplication from this?

thg303 commented 6 years ago

not yet @theebi , It's my first plug-in and I work on it in my spare time.