androidreact-nativecreate-react-native-appreact-native-native-module

NativeModules not included in ReactNative


I just created a new application with create-react-native-app and I detached it to be able to make a native module.

Then, I created SensorManagerPackage.java next to MainApplication.java containing my package. And then created a module satisfying the ReactContextBaseJavaModule implementation.

But When I start my react-native application and I try to log the content of NativeModules, the result is an empty object. Do you have any idea why it doesn't load ?

MainApplication.java

import android.app.Application;

import com.facebook.react.ReactApplication;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.react.shell.MainReactPackage;
import com.facebook.soloader.SoLoader;

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

public class MainApplication extends Application implements ReactApplication {

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

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

    @Override
    protected String getJSMainModuleName() {
      return "index";
    }
  };

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

  @Override
  public void onCreate() {
    super.onCreate();
    SoLoader.init(this, /* native exopackage */ false);
  }
}

SensorManagerPackage.java

import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import com.farmingapp.sensors.AmbientTemperatureSensor;
import com.farmingapp.sensors.LightSensor;
import com.farmingapp.sensors.PressureSensor;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class SensorManagerPackage implements ReactPackage {
    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
        List<NativeModule> modules = new ArrayList<>();
        modules.add(new AmbientTemperatureSensor(reactContext));
        modules.add(new LightSensor(reactContext));
        modules.add(new PressureSensor(reactContext));
        return modules;
    }

    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        return Collections.emptyList();
    }
}

AbstractSensor

import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.support.annotation.Nullable;
import android.util.Log;

import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;

public abstract class AbstractSensor extends ReactContextBaseJavaModule implements SensorEventListener {
    private final ReactApplicationContext reactContext;
    private final SensorManager sensorManager;
    private final Sensor sensor;
    private final int sensorId;
    private double lastReading = (double) System.currentTimeMillis();
    private int interval;

    public AbstractSensor(ReactApplicationContext reactContext, int sensorId) {
        super(reactContext);
        this.sensorId = sensorId;
        this.reactContext = reactContext;
        this.sensorManager = (SensorManager)reactContext.getSystemService(reactContext.SENSOR_SERVICE);
        this.sensor = this.sensorManager.getDefaultSensor(sensorId);
    }

    // RN Methods
    @ReactMethod
    public void setUpdateInterval(int newInterval) {
        this.interval = newInterval;
    }

    @ReactMethod
    public void startUpdates(Promise promise) {
        if (this.sensor == null) {
            // No sensor found, throw error
            promise.reject(new RuntimeException("No sensor found"));
            return;
        }
        promise.resolve(null);
        // Milisecond to Microsecond conversion
        sensorManager.registerListener(this, sensor, this.interval * 1000);
    }

    @ReactMethod
    public void stopUpdates() {
        sensorManager.unregisterListener(this);
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int i) {

    }

    protected void sendEvent(String eventName, @Nullable WritableMap params) {
        try {
            this.reactContext
                    .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                    .emit(eventName, params);
        } catch (RuntimeException e) {
            Log.e("ERROR", "java.lang.RuntimeException: Trying to invoke Javascript before CatalystInstance has been set!");
        }
    }

    protected abstract WritableMap convertEvent(SensorEvent sensorEvent, WritableMap map);

    @Override
    public void onSensorChanged(SensorEvent sensorEvent) {
        double tempMs = (double) System.currentTimeMillis();
        if (tempMs - lastReading >= interval){
            lastReading = tempMs;

            WritableMap map = Arguments.createMap();

            if (sensorEvent.sensor.getType() == this.sensorId) {
                map.putDouble("timestamp", (double) System.currentTimeMillis());
                sendEvent(this.getName(), this.convertEvent(sensorEvent, map));
            }
        }
    }

}

LightSensor.java

import android.hardware.Sensor;
import android.hardware.SensorEvent;

import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.WritableMap;

public class LightSensor extends AbstractSensor {
    public LightSensor(ReactApplicationContext reactContext) {
        super(reactContext, Sensor.TYPE_AMBIENT_TEMPERATURE);
    }

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

    protected WritableMap convertEvent(SensorEvent sensorEvent, WritableMap map) {
        map.putDouble("value", sensorEvent.values[0]);
        return map;
    }

}

Solution

  • I just found the solution to that problem. The issue came from the fact that the @ReactMethod were declared in the AbstractSensor class.

    To fix that I overrided the methods I wanted to expose in the child class and I put the @ReactMethod annotation on them.

    And then, because the modules have now some exposed method, they are now visible in the NativeModules object.