NativeEventEmitterModule making Native Module functions and events unreachable

Adapting the code given from the Chat example I ended up with the code below. Other than the NativeEventEmmiterModule specifics I don’t believe that the code written should impact whether or not it works.

using Fuse;
using Fuse.Scripting;
using Fuse.Reactive;
using Uno.UX;
using Uno.Compiler.ExportTargetInterop;
using Uno;

[Require("Xcode.Framework","CoreMotion.framework")]
[ForeignInclude(Language.ObjC, "Motion.hh")]

[UXGlobalModule]
public class MotionModule : NativeEventEmitterModule
{
	static readonly MotionModule _instance;
	extern(iOS) ObjC.Object _motion;

	public MotionModule() : base (true, "accelerometerChanged", "gyroscopeChanged", "motionChanged", "magnetometerChanged")
	{
		if(_instance != null)
            return;

		_instance = this;
		Resource.SetGlobalKey(_instance, "Motion");

		AddMember(new NativeFunction("Subscribe", (NativeCallback)Subscribe));

        AddMember(new NativeFunction("Unsubscribe", (NativeCallback)Unsubscribe));

		if defined(iOS)
          _motion = AllocMotion();
	}

	[Foreign(Language.ObjC)]
	extern(iOS) ObjC.Object AllocMotion()
	@{
		return [Motion alloc];
	@}

	[Foreign(Language.ObjC)]
	public extern(iOS) bool SubscribeAccelerometer(ObjC.Object motion, double interval, Action<string> callback)
	@{
		[(Motion *)motion getAccelerometerValues: interval withCallback:callback];
    return true;
	@}
  void AccelerometerCallback(string accelerometer)
	{
		Emit("accelerometerChanged", accelerometer);
	}

  [Foreign(Language.ObjC)]
	public extern(iOS) bool SubscribeGyroscope(ObjC.Object motion, double interval, Action<string> callback)
	@{
		[(Motion *)motion getGyroValues: interval withCallback:callback];
    return true;
	@}
  void GyroscopeCallback(string gyroscope)
	{
		Emit("gyroscopeChanged", gyroscope);
	}

  [Foreign(Language.ObjC)]
	public extern(iOS) bool SubscribeMotion(ObjC.Object motion, double interval, Action<string> callback)
	@{
		[(Motion *)motion getMotionValues: interval withCallback:callback];
    return true;
	@}
  void MotionCallback(string motion)
	{
		Emit("motionChanged", motion);
	}

	[Foreign(Language.ObjC)]
	public extern(iOS) bool SubscribeMagnetometer(ObjC.Object motion, double interval, Action<string> callback)
	@{
		[(Motion *)motion getMagnetometerValues: interval withCallback:callback];
    return true;
	@}
  void MagnetometerCallback(string magnetometer)
	{
		Emit("magnetometerChanged", magnetometer);
	}

  object Subscribe(Context c, object[] args)
	{
		//...
	}

  object Unsubscribe(Context c, object[] args)
	{
	   //...
    }
}

Using the code I face errors like these:

Motion.on is not a function. (In 'Motion.on', 'Motion.on' is undefined) in Fuse.Scripting.ScriptModule.RequireContext</usr/local/share/uno/Packages/Fuse.Scripting/0.41.3/$.uno:214>

and

Motion.Subscribe is not a function. (In 'Motion.Subscribe("accelerometer magnetometer gyroscope motion", 0.2)', 'Motion.Subscribe' is undefined)

Since the NativeFunctions are not working at all after the switch to MotionModule : NativeEventEmitterModule from MotionModule: NativeModule I’m lead to believe that there is something weird going on here. However I don’t see any difference between the example and the code I’ve written, so I can’t really debug it further.

I had to remove the ObjC code that referred to Motion, which you didn’t provide, but after doing that the following app works and prints yo.

<App>
  <JavaScript>
    var Motion = require('Motion');
    Motion.on('accelerometerChanged', function() {
      console.log('yo');
    })
    Motion.emit('accelerometerChanged');
  </JavaScript>
</App>

So there has to be something more to it in the code that you’re not posting.

All the ObjC code is working and tested with the older event system. All it does is returning a string to the callbacks which in turn emits the events. Indeed there is something weird going on here. The NativeFunctions also worked with the older system but did not in any way depend on it. It simply returned a bool.

Full Uno code

using Fuse;
using Fuse.Scripting;
using Fuse.Reactive;
using Uno.UX;
using Uno.Compiler.ExportTargetInterop;
using Uno;

[Require("Xcode.Framework","CoreMotion.framework")]
[ForeignInclude(Language.ObjC, "Motion.hh")]

[UXGlobalModule]
public class MotionModule : NativeEventEmitterModule
{
	static readonly MotionModule _instance;
	extern(iOS) ObjC.Object _motion;

	public MotionModule() : base (true, "accelerometerChanged", "gyroscopeChanged", "motionChanged", "magnetometerChanged")
	{
		if(_instance != null)
      return;

		_instance = this;
		Resource.SetGlobalKey(_instance, "Motion");

		AddMember(new NativeFunction("Subscribe", (NativeCallback)Subscribe));

    AddMember(new NativeFunction("Unsubscribe", (NativeCallback)Unsubscribe));

		if defined(iOS)
      _motion = AllocMotion();
	}

	[Foreign(Language.ObjC)]
	extern(iOS) ObjC.Object AllocMotion()
	@{
		return [Motion alloc];
	@}

	[Foreign(Language.ObjC)]
	public extern(iOS) bool SubscribeAccelerometer(ObjC.Object motion, double interval, Action<string> callback)
	@{
		[(Motion *)motion getAccelerometerValues: interval withCallback:callback];
    return true;
	@}
  void AccelerometerCallback(string accelerometer)
	{
		Emit("accelerometerChanged", accelerometer);
	}

  [Foreign(Language.ObjC)]
	public extern(iOS) bool SubscribeGyroscope(ObjC.Object motion, double interval, Action<string> callback)
	@{
		[(Motion *)motion getGyroValues: interval withCallback:callback];
    return true;
	@}
  void GyroscopeCallback(string gyroscope)
	{
		Emit("gyroscopeChanged", gyroscope);
	}

  [Foreign(Language.ObjC)]
	public extern(iOS) bool SubscribeMotion(ObjC.Object motion, double interval, Action<string> callback)
	@{
		[(Motion *)motion getMotionValues: interval withCallback:callback];
    return true;
	@}
  void MotionCallback(string motion)
	{
		Emit("motionChanged", motion);
	}

	[Foreign(Language.ObjC)]
	public extern(iOS) bool SubscribeMagnetometer(ObjC.Object motion, double interval, Action<string> callback)
	@{
		[(Motion *)motion getMagnetometerValues: interval withCallback:callback];
    return true;
	@}
  void MagnetometerCallback(string magnetometer)
	{
		Emit("magnetometerChanged", magnetometer);
	}

  object Subscribe(Context c, object[] args)
	{
		if defined(iOS){
      if(args.Length != 2)
        return false;

      bool subscribed = false;

      if(args[0].ToString().Contains("accelerometer"))
        subscribed = SubscribeAccelerometer(_motion, Marshal.ToDouble(args[1]), AccelerometerCallback);
      if(args[0].ToString().Contains("gyroscope"))
        subscribed = SubscribeGyroscope(_motion, Marshal.ToDouble(args[1]), GyroscopeCallback);
      if(args[0].ToString().Contains("motion"))
        subscribed = SubscribeMotion(_motion, Marshal.ToDouble(args[1]), MotionCallback);
			if(args[0].ToString().Contains("magnetometer"))
        subscribed = SubscribeMagnetometer(_motion, Marshal.ToDouble(args[1]), MagnetometerCallback);

      return subscribed;
    } else {
      debug_log "Motion is only implemented for iOS";

      return false;
    }
	}

  object Unsubscribe(Context c, object[] args)
	{
		if defined(iOS){
      if(args.Length == 0)
        return false;

      bool unsubscribed = false;
      /*
      if(args[0].Contains("accelerometer"))
        unsubscribed = SubscribeAccelerometer(_motion, AccelerometerCallback);
      if(args[0].Contains("gyroscope"))
        unsubscribed = SubscribeGyroscope(_motion, GyroscopeCallback);
      if(args[0].Contains("motion"))
        unsubscribed = SubscribeMotion(_motion, MotionCallback);
      */
      return unsubscribed;
    } else {
      debug_log "Motion is only implemented for iOS";

      return false;
    }
	}
}

Full C code (although it should not impact anything)

Motion.hh

#import <CoreMotion/CoreMotion.h>

@interface Motion : NSObject

@property (strong,nonatomic) CMMotionManager *manager;

@property (nonatomic) double previousAttitudeYaw;

- (void) getAccelerometerValues: (double)interval withCallback:(void(^)(NSString*)) callback;

- (void) getGyroValues: (double)interval withCallback:(void(^)(NSString*)) callback;

- (void) getMagnetometerValues: (double)interval withCallback:(void(^)(NSString*)) callback;

- (void) getMotionValues: (double)interval withCallback:(void(^)(NSString*)) callback;

@end

Motion.mm

#import "Motion.hh"

//TODO: add magnetometer support
//http://stackoverflow.com/questions/11711646/why-am-i-getting-0-degrees-from-magneticfield-property-the-whole-time

@implementation Motion

-(id)init {
  if (self = [super init]) {

  }
  return self;
}

/*
//Register for Coremotion notifications
[self.motionActivityManager startActivityUpdatesToQueue:[NSOperationQueue mainQueue] withHandler:^(CMMotionActivity *activity)
{
  NSLog(@"Got a core motion update");
  NSLog(@"Current activity date is %f",activity.timestamp);
  NSLog(@"Current activity confidence from a scale of 0 to 2 - 2 being best- is: %ld",activity.confidence);
  NSLog(@"Current activity type is unknown: %i",activity.unknown);
  NSLog(@"Current activity type is stationary: %i",activity.stationary);
  NSLog(@"Current activity type is walking: %i",activity.walking);
  NSLog(@"Current activity type is running: %i",activity.running);
  NSLog(@"Current activity type is automotive: %i",activity.automotive);
}];
*/

//Continously retrieve 3D accelerometer values measured in G:s
- (void) getAccelerometerValues: (double)interval withCallback:(void(^)(NSString*)) callback{
  if(self.manager == nil)
      self.manager = [[CMMotionManager alloc] init];

  if(self.manager.accelerometerAvailable){
    self.manager.accelerometerUpdateInterval = interval;

    [self.manager startAccelerometerUpdatesToQueue:[NSOperationQueue mainQueue]
     withHandler:^(CMAccelerometerData *accelerometer, NSError *error)
     {
       if(error != nil){
         callback([NSString stringWithFormat:@"{\
           \"error\": {\"description\": %@}}",
           error.localizedDescription
           ]);

         return;
       }

        callback([NSString stringWithFormat:@"{\"acceleration\":{\"x\": %f, \"y\": %f, \"z\": %f}}",
          accelerometer.acceleration.x, accelerometer.acceleration.y, accelerometer.acceleration.z
          ]);
     }];
  }
}

//Continously retrieve 3D rotation rates measured in angles
- (void) getGyroValues: (double)interval withCallback:(void(^)(NSString*)) callback{
  if(self.manager == nil)
      self.manager = [[CMMotionManager alloc] init];

  if(self.manager.gyroAvailable){
    self.manager.gyroUpdateInterval = interval;

    [self.manager startGyroUpdatesToQueue:[NSOperationQueue mainQueue]
     withHandler:^(CMGyroData *gyro, NSError *error)
     {
       if(error != nil){
         callback([NSString stringWithFormat:@"{\
           \"error\": {\"description\": %@}}",
           error.localizedDescription
           ]);

         return;
       }

         //Return in degrees instead of radians
         double degreesFromRadian = 180 / M_PI;

         callback([NSString stringWithFormat:@"{\"rotationRate\":{\"x\": %f, \"y\": %f, \"z\": %f}}",
           degreesFromRadian * gyro.rotationRate.x, degreesFromRadian * gyro.rotationRate.y, degreesFromRadian * gyro.rotationRate.z
           ]);
     }];
  }
}

- (void) getMagnetometerValues: (double)interval withCallback:(void(^)(NSString*)) callback{
  if(self.manager == nil)
      self.manager = [[CMMotionManager alloc] init];

  if(self.manager.magnetometerAvailable){
    self.manager.magnetometerUpdateInterval = interval;

    [self.manager startMagnetometerUpdatesToQueue:[NSOperationQueue mainQueue]
     withHandler:^(CMMagnetometerData *magnetometer, NSError *error)
     {
       if(error != nil){
         callback([NSString stringWithFormat:@"{\
           \"error\": {\"description\": %@}}",
           error.localizedDescription
           ]);

         return;
       }

        callback([NSString stringWithFormat:@"{\"magneticField\": {\"x\": %f, \"y\": %f, \"z\": %f}}",
          magnetometer.magneticField.x, magnetometer.magneticField.y, magnetometer.magneticField.z
          ]);
     }];
  }
}

- (void) getMotionValues: (double)interval withCallback:(void(^)(NSString*)) callback{
    if(self.manager == nil)
        self.manager = [[CMMotionManager alloc] init];

  if(self.manager.deviceMotionAvailable){
    self.manager.deviceMotionUpdateInterval = interval;

    [self.manager startDeviceMotionUpdatesToQueue:[NSOperationQueue mainQueue]
     withHandler:^(CMDeviceMotion *motion, NSError *error)
     {
       if(error != nil){
         callback([NSString stringWithFormat:@"{\
           \"error\": {\"description\": %@}}",
           error.localizedDescription
           ]);

         return;
       }

         //Return in degrees instead of radians
         double degreesFromRadian = 180 / M_PI;

         CMQuaternion quat = motion.attitude.quaternion;
         double yaw = asin(2*(quat.x*quat.z - quat.w*quat.y));

         if (self.previousAttitudeYaw == 0) {
             self.previousAttitudeYaw = yaw;
         }

         // kalman filtering
         static float q = 0.1;   // process noise
         static float r = 0.1;   // sensor noise
         static float p = 0.1;   // estimated error
         static float k = 0.5;   // kalman filter gain

         float x = self.previousAttitudeYaw;
         p = p + q;
         k = p / (p + r);
         x = x + k*(yaw - x);
         p = (1 - k)*p;
         self.previousAttitudeYaw = x;

        callback([NSString stringWithFormat:@"{\
          \"attitude\": {\"yaw\": %f, \"pitch\": %f, \"roll\": %f}, \
           \"rotationRate\": {\"x\": %f, \"y\": %f, \"z\": %f}, \
           \"gravity\": {\"x\": %f, \"y\": %f, \"z\": %f}, \
           \"magneticField\": {\"accuracy\": %d, \"x\": %f, \"y\": %f, \"z\": %f}, \
           \"userAcceleration\": {\"x\": %f, \"y\": %f, \"z\": %f}}",
           x * degreesFromRadian, motion.attitude.pitch * degreesFromRadian, motion.attitude.roll * degreesFromRadian,
           motion.rotationRate.x, motion.rotationRate.y, motion.rotationRate.z,
           motion.gravity.x, motion.gravity.y, motion.gravity.z,
           motion.magneticField.accuracy, motion.magneticField.field.x, motion.magneticField.field.y, motion.magneticField.field.z,
           motion.userAcceleration.x, motion.userAcceleration.y, motion.userAcceleration.z
           ]);
     }];
  }
}

@end

That code also works fine here, so the problem might for example be in how you actually require the module.

Am I missing something?
Does the class have to have the same name as mentioned in Resource.SetGlobalKey(_instance, "Motion");?

var Motion = require("Motion");

Motion.on("accelerometerChanged", function(values){
  
});

Motion.Subscribe("accelerometer magnetometer gyroscope motion", 0.2);

The following code prints accelerometer changed repeatedly in the log window for me:

<App>
  <JavaScript>
    var Motion = require("Motion");

    Motion.on("accelerometerChanged", function(values){
      console.log('accelerometer changed');
    });

    Motion.Subscribe("accelerometer magnetometer gyroscope motion", 0.2);
  </JavaScript>
</App>

Perhaps you can send the full project to me either on Slack or through https://www.dropbox.com/request/ZgndLtJQm5eGzG9cicGK? Thanks!

After spending some more time trying to debug it for myself (since it seems like I’ve just made a stupid mistake somewhere) I still can’t find the error. I went ahead and sent you the project. Thanks for helping!

The problem is that there are two requirable things called Motion: The file Motion.js and the native Uno module. If you rename Motion.js to e.g. MotionJS.js it works.

I’ll make an internal issue on making this an error in the future as it’s very confusing.

Ah, I see. Should’ve tried that. Thank you for all the help!