1

I want to access the Objective-C EKEventStore in Java via Rococoa. The API specifies a callback to get notified when the user accepts the calendar access prompt, which works perfectly in pure Objective-C.

    Native.loadLibrary("EventKit", EventKitLibrary.class);

    EKEventStore store = EKEventStore.CLASS.alloc();
    store.init();
    //store = store.initWithAccessToEntityTypes(EKEntityType.EKEntityTypeEvent); // no notification
    EKEventStoreRequestAccessCompletionHandler handler = new EKEventStoreRequestAccessCompletionHandler() {
        @Override
        public void invoke(boolean granted, Pointer error) {
            System.out.println("Access: " + granted);
            NSArray calArray = store.calendarsForEntityType(EKEntityType.EKEntityTypeEvent);
            for (int i = 0; i < calArray.count(); i++) {
                NSObject calObject = calArray.objectAtIndex(i);
                EKCalendar osxcal = Rococoa.cast(calObject, EKCalendar.class);
                System.out.println(osxcal.title().toString());
            }
        }

    };
    ObjCObject object = Rococoa.proxy(handler); // get Objective C Callback Object to send
    store.requestAccessToEntityType_completion(EKEntityType.EKEntityTypeEvent, object.id());

    try {
        Thread.sleep(10000); // wait for the access prompt
    } catch (InterruptedException ex) {
    }

    // random object access to save instances from gc
    System.out.println(handler.toString());
    System.out.println(store.id());
    System.out.println(object.id());

The Library

public interface EventKitLibrary extends Library {

    public static EventKitLibrary INSTANCE = (EventKitLibrary) Native.loadLibrary("EventKit", EventKitLibrary.class);

}

The Mapped Classes

public abstract class EKEventStore extends NSObject {

    public static final _Class CLASS = Rococoa.createClass("EKEventStore", _Class.class);

    public interface _Class extends ObjCClass {

        public abstract EKEventStore alloc();
    }

    public static interface EKEntityType {

        public static final int EKEntityTypeEvent = 0;
        public static final int EKEntityTypeReminder = 1;
    };

    public static interface EKEntityMask {

        public static final int EKEntityMaskEvent = (1 << EKEntityType.EKEntityTypeEvent);
        public static final int EKEntityMaskReminder = (1 << EKEntityType.EKEntityTypeReminder);
    };

    public abstract EKEventStore initWithAccessToEntityTypes(int EKEntityMask);

    public abstract EKEventStore init();

    public abstract void requestAccessToEntityType_completion(int EKEntityType, ID handler);

    interface EKEventStoreRequestAccessCompletionHandler {

        void invoke(boolean granted, Pointer error);
    }

    public abstract NSArray calendarsForEntityType(int EKEntityType);
}


public abstract class EKCalendar extends NSObject {

    public static final _Class CLASS = Rococoa.createClass("EKCalendar", _Class.class);

    public static interface _Class extends ObjCClass {

        public NSObject alloc();
    }

    public abstract NSString title();
}

I only get an IllegalArgumentException for a missing type conversion of the NSError parameter. Am I doing something wrong, or should I implement a TypeConverter? And if, how should I do that?

EDIT:

Now I am using Pointer instead of NSError as parameter for the callback function, and I get the following JVM-Crash.

EDIT2:

Now I am using the Rococoa.proxy(handler) function for the callback like in the Rococoa Library. The input prompt appears, but the callback function doesn't get called. I think my callback initialization is still wrong.

Timon
  • 11
  • 3
  • If rococoa doesn't provide a `Pointer`->`NSError` type mapper you can certainly make do with using `Pointer` as the argument type, which should get you at least to the point of executing the callback successfully, after which point you can investigate the best manner of handling the `NSError` type. – technomage Sep 04 '15 at 15:28
  • [This usage](https://github.com/iterate-ch/rococoa/blob/f064cd4257ff4766409969930f28a57fad5baae9/rococoa/rococoa-contrib/src/main/java/org/rococoa/contrib/appkit/NSSpeechSynthesizer.java#L206) might be a correct conversion from `Pointer` to `NSError`. – technomage Sep 04 '15 at 15:34
  • I also tried that before, but I get a JVM fatal error # SIGSEGV (0xb) at pc=0x00007fff8361c064, pid=19097, tid=5379 # C [libobjc.A.dylib+0x9064] objc_retain+0x14 siginfo: si_signo: 11 (SIGSEGV), si_code: 0 (unknown), si_addr: 0x0000000000000000 – Timon Sep 05 '15 at 20:45
  • Update your question with those results and a description of exactly what you tried. – technomage Sep 05 '15 at 21:03
  • ok, the Pointer works perfect now, the function gets called correctly, but the callback function is never executed – Timon Sep 06 '15 at 01:05
  • ObjC callbacks are definitely a different beast, although your case has [been accounted for](https://github.com/acharneski/rococoa/commit/6abb5b44cd03a6cc0d891988f4469f1e62bfb1cd). – technomage Sep 06 '15 at 19:38

1 Answers1

1

Generally, a TypeMapper is implemented like this which converts a Pointer native type into some other Java type:

class NSErrorTypeMapper extends DefaultTypeMapper {
    public NSErrorTypeMapper() {
        TypeConverter tc = new TypeConverter() {
            public Object toNative(Object value, ToNativeContext ctxt) {
                Pointer p = // convert your NSError "value" into a Pointer
                return p;
            }
            public Object fromNative(Object value, FromNativeContext ctxt) {
                Pointer p = (Pointer)value;
                Object object = // convert the pointer into an NSError object
                return object;    
            }
            public class nativeType() {
                return Pointer.class;
            }
        };
        addToNativeConverter(NSError.class, tc);
        addFromNativeConverter(NSError.class, tc);
    }
}
technomage
  • 9,861
  • 2
  • 26
  • 40