Xamarin-macios: [Siri Intents] Auto-generated intents doesn't work properly

Created on 2 Nov 2018  路  11Comments  路  Source: xamarin/xamarin-macios

Steps to Reproduce

  1. Download and open a sample
  2. Open SoupChef.Shared -> Data -> SoupOrderDataManager.cs and go to the line 38
  3. Run it on a device
  4. Add a new item in the app (tap on + -> tap on any item -> tap on Place Order)
  5. Look at the output

Expected Behavior

There is Successfully donated interaction. message.

Actual Behavior

There is Interaction donation failed message.
Error is: Cannot donate interaction with intent that has no valid shortcut types

This code creates an INInteraction instance from the custom Intent.


Also there is another way to get a runtime exception:

  1. Open SoupChef.Shared -> Data -> SoupMenuManager.cs
  2. Uncomment line 119
  3. Go to the line 223
  4. Uninstall the application from the device
  5. Run it

You will get Could not initialize an instance of the type 'Intents.INShortcut': the native 'initWithIntent:' method returned nil. exception.
_Stack trace: https://gist.github.com/mykyta-bondarenko-gl/07680ecf25d7011b6455aa1f69332e10_

Additional Information

I've create a new intents file via new VSfM UI. Also I've tried to use an old intents file and the original one (from the Xcode project)
We've discussed this issue with @jstedfast and decided that we need an additional help to found the reason of these issues

Environment

  1. VSfM
    https://gist.github.com/mykyta-bondarenko-gl/50a460f96cc0f95f8106cb31cabd79ea

Build Logs

  1. Build Output
    https://gist.github.com/mykyta-bondarenko-gl/8afb8ba2591386e9ae9f7274a2b458d1

Example Project

Sample

iOS question

All 11 comments

For reference, these are the source files that Xcode generates:

//
// OrderSoupIntent.h
//
// This file was automatically generated and should not be edited.
//

#import <Intents/Intents.h>

NS_ASSUME_NONNULL_BEGIN

API_AVAILABLE(ios(12.0), watchos(5.0))
@interface OrderSoupIntent : INIntent

@property (readwrite, copy, nullable, nonatomic) INObject *soup;
@property (readwrite, copy, nullable, nonatomic) NSNumber *quantity;
@property (readwrite, copy, nullable, nonatomic) NSArray<INObject *> *options;

@end

@class OrderSoupIntentResponse;

/*!
 @abstract Protocol to declare support for handling a OrderSoupIntent. By implementing this protocol, a class can provide logic for resolving, confirming and handling the intent.
 @discussion The minimum requirement for an implementing class is that it should be able to handle the intent. The confirmation method is optional. The handling method is always called last, after confirming the intent.
 */
API_AVAILABLE(ios(12.0), watchos(5.0))
@protocol OrderSoupIntentHandling <NSObject>

@required

/*!
 @abstract Handling method - Execute the task represented by the OrderSoupIntent that's passed in
 @discussion Called to actually execute the intent. The app must return a response for this intent.

 @param  intent The input intent
 @param  completion The response handling block takes a OrderSoupIntentResponse containing the details of the result of having executed the intent

 @see  OrderSoupIntentResponse
 */
- (void)handleOrderSoup:(OrderSoupIntent *)intent completion:(void (^)(OrderSoupIntentResponse *response))completion NS_SWIFT_NAME(handle(intent:completion:));

@optional

/*!
 @abstract Confirmation method - Validate that this intent is ready for the next step (i.e. handling)
 @discussion Called prior to asking the app to handle the intent. The app should return a response object that contains additional information about the intent, which may be relevant for the system to show the user prior to handling. If unimplemented, the system will assume the intent is valid, and will assume there is no additional information relevant to this intent.

 @param  intent The input intent
 @param  completion The response block contains a OrderSoupIntentResponse containing additional details about the intent that may be relevant for the system to show the user prior to handling.

 @see OrderSoupIntentResponse
 */
- (void)confirmOrderSoup:(OrderSoupIntent *)intent completion:(void (^)(OrderSoupIntentResponse *response))completion NS_SWIFT_NAME(confirm(intent:completion:));

@end

/*!
 @abstract Constants indicating the state of the response.
 */
typedef NS_ENUM(NSInteger, OrderSoupIntentResponseCode) {
    OrderSoupIntentResponseCodeUnspecified = 0,
    OrderSoupIntentResponseCodeReady,
    OrderSoupIntentResponseCodeContinueInApp,
    OrderSoupIntentResponseCodeInProgress,
    OrderSoupIntentResponseCodeSuccess,
    OrderSoupIntentResponseCodeFailure,
    OrderSoupIntentResponseCodeFailureRequiringAppLaunch,
    OrderSoupIntentResponseCodeFailureSoupUnavailable = 100
} API_AVAILABLE(ios(12.0), watchos(5.0));

API_AVAILABLE(ios(12.0), watchos(5.0))
@interface OrderSoupIntentResponse : INIntentResponse

- (instancetype)init NS_UNAVAILABLE;

/*!
 @abstract Initializes the response object with the specified code and user activity object.
 @discussion The app extension has the option of capturing its private state as an NSUserActivity and returning it as the 'currentActivity'. If the app is launched, an NSUserActivity will be passed in with the private state. The NSUserActivity may also be used to query the app's UI extension (if provided) for a view controller representing the current intent handling state. In the case of app launch, the NSUserActivity will have its activityType set to the name of the intent. This intent object will also be available in the NSUserActivity.interaction property.

 @param  code The response code indicating your success or failure in confirming or handling the intent.
 @param  userActivity The user activity object to use when launching your app. Provide an object if you want to add information that is specific to your app. If you specify nil, the system automatically creates a user activity object for you, sets its type to the class name of the intent being handled, and fills it with an INInteraction object containing the intent and your response.
 */
- (instancetype)initWithCode:(OrderSoupIntentResponseCode)code userActivity:(nullable NSUserActivity *)userActivity NS_DESIGNATED_INITIALIZER;

/*!
 @abstract Initializes and returns the response object with the success code.
 */
+ (instancetype)successIntentResponseWithSoup:(INObject *)soup waitTime:(NSNumber *)waitTime NS_SWIFT_NAME(success(soup:waitTime:));
/*!
 @abstract Initializes and returns the response object with the failureSoupUnavailable code.
 */
+ (instancetype)failureSoupUnavailableIntentResponseWithSoup:(INObject *)soup NS_SWIFT_NAME(failureSoupUnavailable(soup:));

@property (readwrite, copy, nullable, nonatomic) INObject *soup;
@property (readwrite, copy, nullable, nonatomic) NSNumber *waitTime;

/*!
 @abstract The response code indicating your success or failure in confirming or handling the intent.
 */
@property (readonly, NS_NONATOMIC_IOSONLY) OrderSoupIntentResponseCode code;

@end

NS_ASSUME_NONNULL_END
//
// OrderSoupIntent.m
//
// This file was automatically generated and should not be edited.
//

#import "OrderSoupIntent.h"

@implementation OrderSoupIntent

@dynamic soup, quantity, options;

@end

@interface OrderSoupIntentResponse ()

@property (readwrite, NS_NONATOMIC_IOSONLY) OrderSoupIntentResponseCode code;

@end

@implementation OrderSoupIntentResponse

@synthesize code = _code;

@dynamic soup, waitTime;

- (instancetype)initWithCode:(OrderSoupIntentResponseCode)code userActivity:(nullable NSUserActivity *)userActivity {
    self = [super init];
    if (self) {
        _code = code;
        self.userActivity = userActivity;
    }
    return self;
}

+ (instancetype)successIntentResponseWithSoup:(INObject *)soup waitTime:(NSNumber *)waitTime {
    OrderSoupIntentResponse *intentResponse = [[OrderSoupIntentResponse alloc] initWithCode:OrderSoupIntentResponseCodeSuccess userActivity:nil];
    intentResponse.soup = soup;
    intentResponse.waitTime = waitTime;
    return intentResponse;
}

+ (instancetype)failureSoupUnavailableIntentResponseWithSoup:(INObject *)soup {
    OrderSoupIntentResponse *intentResponse = [[OrderSoupIntentResponse alloc] initWithCode:OrderSoupIntentResponseCodeFailureSoupUnavailable userActivity:nil];
    intentResponse.soup = soup;
    return intentResponse;
}

@end

And these are the source files that MSBuild generates:

using System;
using System.Runtime.InteropServices;

using Intents;
using Foundation;
using ObjCRuntime;
using CoreLocation;

namespace SoupChef {
    [Register ("OrderSoupIntent")]
    class OrderSoupIntent : INIntent
    {
        public OrderSoupIntent () : base (NSObjectFlag.Empty) { }

        protected OrderSoupIntent (IntPtr handle) : base (handle) { }

        [Export ("soup", ArgumentSemantic.Copy)]
        public INObject Soup { get; set; }

        [Export ("quantity", ArgumentSemantic.Copy)]
        public NSNumber Quantity { get; set; }

        [Export ("options", ArgumentSemantic.Copy)]
        public NSArray<INObject> Options { get; set; }
    }

    [Native]
    enum OrderSoupIntentResponseCode : long
    {
        Unspecified,
        Ready,
        ContinueInApp,
        InProgress,
        Success,
        Failure,
        FailureRequiringAppLaunch,
        FailureSoupUnavailable = 100,
    }

    [Register ("OrderSoupIntentResponse")]
    class OrderSoupIntentResponse : INIntentResponse
    {
        [Export ("initWithCode:userActivity:")]
        public OrderSoupIntentResponse (OrderSoupIntentResponseCode code, NSUserActivity userActivity)
        {
            Code = code;
            UserActivity = userActivity;
        }

        [Export ("successIntentResponseWithSoup:waitTime:")]
        public static OrderSoupIntentResponse SuccessIntentResponseWithSoup (INObject soup, NSNumber waitTime)
        {
            return new OrderSoupIntentResponse (OrderSoupIntentResponseCode.Success, null) {
                Soup = soup,
                WaitTime = waitTime,
            };
        }

        [Export ("failureSoupUnavailableIntentResponseWithSoup:")]
        public static OrderSoupIntentResponse FailureSoupUnavailableIntentResponseWithSoup (INObject soup)
        {
            return new OrderSoupIntentResponse (OrderSoupIntentResponseCode.FailureSoupUnavailable, null) {
                Soup = soup,
            };
        }

        public INObject Soup {
            [Export ("soup")]
            get;
            [Export ("setSoup:")]
            set;
        }

        public NSNumber WaitTime {
            [Export ("waitTime")]
            get;
            [Export ("setWaitTime:")]
            set;
        }

        [Export ("code")]
        public OrderSoupIntentResponseCode Code { get; }
    }

    delegate void OrderSoupResponseCallback (OrderSoupIntentResponse response);

    [BindingImpl (BindingImplOptions.GeneratedCode | BindingImplOptions.Optimizable)]
    static class OrderSoupIntentHandlingTrampolines
    {
        const string LIBOBJC_DYLIB = "/usr/lib/libobjc.dylib";

        [DllImport (LIBOBJC_DYLIB, EntryPoint="objc_msgSend")]
        public extern static IntPtr IntPtr_objc_msgSend (IntPtr receiever, IntPtr selector);
        [DllImport (LIBOBJC_DYLIB, EntryPoint="objc_msgSendSuper")]
        public extern static IntPtr IntPtr_objc_msgSendSuper (IntPtr receiever, IntPtr selector);
        [DllImport (LIBOBJC_DYLIB, EntryPoint="objc_msgSend")]
        public extern static IntPtr IntPtr_objc_msgSend_IntPtr (IntPtr receiever, IntPtr selector, IntPtr arg1);
        [DllImport (LIBOBJC_DYLIB, EntryPoint="objc_msgSendSuper")]
        public extern static IntPtr IntPtr_objc_msgSendSuper_IntPtr (IntPtr receiever, IntPtr selector, IntPtr arg1);
        [DllImport (LIBOBJC_DYLIB, EntryPoint="objc_msgSend")]
        public extern static void void_objc_msgSend_IntPtr (IntPtr receiver, IntPtr selector, IntPtr arg1);
        [DllImport (LIBOBJC_DYLIB, EntryPoint="objc_msgSendSuper")]
        public extern static void void_objc_msgSendSuper_IntPtr (IntPtr receiver, IntPtr selector, IntPtr arg1);
        [DllImport (LIBOBJC_DYLIB, EntryPoint="objc_msgSend")]
        public extern static void void_objc_msgSend_IntPtr_IntPtr (IntPtr receiver, IntPtr selector, IntPtr arg1, IntPtr arg2);
        [DllImport (LIBOBJC_DYLIB, EntryPoint="objc_msgSendSuper")]
        public extern static void void_objc_msgSendSuper_IntPtr_IntPtr (IntPtr receiver, IntPtr selector, IntPtr arg1, IntPtr arg2);
        [DllImport (LIBOBJC_DYLIB)]
        static extern IntPtr _Block_copy (IntPtr ptr);

        [UnmanagedFunctionPointerAttribute (CallingConvention.Cdecl)]
        [UserDelegateType (typeof (OrderSoupResponseCallback))]
        internal delegate void DOrderSoupResponseCallback (IntPtr block, IntPtr response);

        static internal class SDOrderSoupResponseCallback
        {
            static internal readonly DOrderSoupResponseCallback Handler = Invoke;

            [MonoPInvokeCallback (typeof (DOrderSoupResponseCallback))]
            static unsafe void Invoke (IntPtr block, IntPtr response)
            {
                var descriptor = (BlockLiteral*) block;
                var del = (OrderSoupResponseCallback) (descriptor->Target);
                if (del != null)
                    del (Runtime.GetNSObject<OrderSoupIntentResponse> (response));
            }
        }

        internal class NIDOrderSoupResponseCallback
        {
            IntPtr blockPtr;
            DOrderSoupResponseCallback invoker;

            [Preserve (Conditional=true)]
            [BindingImpl (BindingImplOptions.GeneratedCode | BindingImplOptions.Optimizable)]
            public unsafe NIDOrderSoupResponseCallback (BlockLiteral *block)
            {
                blockPtr = _Block_copy ((IntPtr) block);
                invoker = block->GetDelegateForBlock<DOrderSoupResponseCallback> ();
            }

            [Preserve (Conditional=true)]
            [BindingImpl (BindingImplOptions.GeneratedCode | BindingImplOptions.Optimizable)]
            ~NIDOrderSoupResponseCallback ()
            {
                Runtime.ReleaseBlockOnMainThread (blockPtr);
            }

            [Preserve (Conditional=true)]
            [BindingImpl (BindingImplOptions.GeneratedCode | BindingImplOptions.Optimizable)]
            public unsafe static OrderSoupResponseCallback Create (IntPtr block)
            {
                if (block == IntPtr.Zero)
                    return null;
                if (BlockLiteral.IsManagedBlock (block)) {
                    var existing_delegate = ((BlockLiteral *) block)->Target as OrderSoupResponseCallback;
                    if (existing_delegate != null)
                        return existing_delegate;
                }
                return new NIDOrderSoupResponseCallback ((BlockLiteral *) block).Invoke;
            }

            [Preserve (Conditional=true)]
            [BindingImpl (BindingImplOptions.GeneratedCode | BindingImplOptions.Optimizable)]
            unsafe void Invoke (OrderSoupIntentResponse response)
            {
                invoker (blockPtr, response == null ? IntPtr.Zero : response.Handle);
            }
        }
    }

    [Protocol (Name = "OrderSoupIntentHandling", WrapperType = typeof (OrderSoupIntentHandlingWrapper))]
    [ProtocolMember (IsRequired = true, IsProperty = false, IsStatic = false, Name = "HandleOrderSoup", Selector = "handleOrderSoup:completion:", ParameterType = new Type [] { typeof (OrderSoupIntent), typeof (OrderSoupResponseCallback) }, ParameterByRef = new bool [] { false, false }, ParameterBlockProxy = new Type [] { null, typeof (OrderSoupIntentHandlingTrampolines.NIDOrderSoupResponseCallback) })]
    [ProtocolMember (IsRequired = false, IsProperty = false, IsStatic = false, Name = "ConfirmOrderSoup", Selector = "confirmOrderSoup:completion:", ParameterType = new Type [] { typeof (OrderSoupIntent), typeof (OrderSoupResponseCallback) }, ParameterByRef = new bool [] { false, false }, ParameterBlockProxy = new Type [] { null, typeof (OrderSoupIntentHandlingTrampolines.NIDOrderSoupResponseCallback) })]
    interface IOrderSoupIntentHandling : INativeObject, IDisposable
    {
        [Export ("handleOrderSoup:completion:")]
        [Preserve (Conditional = true)]
        void HandleOrderSoup (OrderSoupIntent intent, [BlockProxy (typeof (OrderSoupIntentHandlingTrampolines.NIDOrderSoupResponseCallback))]OrderSoupResponseCallback response);
    }

    static partial class OrderSoupIntentHandlingExtensions
    {
        [BindingImpl (BindingImplOptions.GeneratedCode | BindingImplOptions.Optimizable)]
        public unsafe static void ConfirmOrderSoup (this IOrderSoupIntentHandling This, OrderSoupIntent intent, [BlockProxy (typeof (OrderSoupIntentHandlingTrampolines.NIDOrderSoupResponseCallback))]OrderSoupResponseCallback response)
        {
            if (intent == null)
                throw new ArgumentNullException ("intent");

            if (response == null)
                throw new ArgumentNullException ("response");

            BlockLiteral *block_ptr_response;
            BlockLiteral block_response;

            block_response = new BlockLiteral ();
            block_ptr_response = &block_response;
            block_response.SetupBlockUnsafe (OrderSoupIntentHandlingTrampolines.SDOrderSoupResponseCallback.Handler, response);

            OrderSoupIntentHandlingTrampolines.void_objc_msgSend_IntPtr_IntPtr (This.Handle, Selector.GetHandle ("confirmOrderSoup:completion:"), intent.Handle, (IntPtr) block_ptr_response);
            block_ptr_response->CleanupBlock ();
        }
    }

    sealed class OrderSoupIntentHandlingWrapper : BaseWrapper, IOrderSoupIntentHandling
    {
        [Preserve (Conditional = true)]
        public OrderSoupIntentHandlingWrapper (IntPtr handle, bool owns) : base (handle, owns)
        {
        }

        [Export ("handleOrderSoup:completion:")]
        [BindingImpl (BindingImplOptions.GeneratedCode | BindingImplOptions.Optimizable)]
        public unsafe void HandleOrderSoup (OrderSoupIntent intent, [BlockProxy (typeof (OrderSoupIntentHandlingTrampolines.NIDOrderSoupResponseCallback))]OrderSoupResponseCallback response)
        {
            if (intent == null)
                throw new ArgumentNullException ("intent");

            if (response == null)
                throw new ArgumentNullException ("response");

            BlockLiteral *block_ptr_response;
            BlockLiteral block_response;

            block_response = new BlockLiteral ();
            block_ptr_response = &block_response;
            block_response.SetupBlockUnsafe (OrderSoupIntentHandlingTrampolines.SDOrderSoupResponseCallback.Handler, response);

            OrderSoupIntentHandlingTrampolines.void_objc_msgSend_IntPtr_IntPtr (intent.Handle, Selector.GetHandle ("handleOrderSoup:completion:"), intent.Handle, (IntPtr) block_ptr_response);
            block_ptr_response->CleanupBlock ();
        }
    }
}

IIRC that's not merged so we need a special VSfM package. Can you link the one used ?

There is Successfully donated interaction. message.

That's what you get with the Xamarin sample with Xcode generated bindings right @mykyta-bondarenko-gl ?

That's what you get with the Xamarin sample with Xcode generated bindings right

@spouliot no, I get these issues with XI/VSfM generated bindings

@mykyta-bondarenko-gl

There is Successfully donated interaction. message.

^ you're getting the successful message with XI/VSfM generated bindings ?

I want to know if your expected results is something you have (without Jeff works) using Xamarin tools - so we not chasing two different kind of issues (but only the difference with Jeff's generated bindings)

Oh, sorry, my bad. I get sucessfull message with the master version of the sample. There is separate binding library.
@spouliot ^

@mykyta-bondarenko-gl btw, when I went to build your sample after replacing your Intents.intentdefinition file with the file from the master sample, they have different enum success/failure states and property types.

For example, instead of FailureSoupUnavailable, you had something else.

Your waitTime was also an NSString instead of a NSNumber.

It would probably be helpful to stick to doing a verbatim port to start with so we eliminate possible differences in the app logic as the potential source of problems.

The changes you are making to the sample are probably nice changes overall, but they do introduce new uncertainties.

@jstedfast yes, because there are several complex changes in the native sample and I made them already. You can just comment code where you get compile errors, as far as I remeber they doesn't affect base logic

Yea, that's what I did.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

wcoder picture wcoder  路  3Comments

nickplee picture nickplee  路  3Comments

mrward picture mrward  路  4Comments

juepiezhongren picture juepiezhongren  路  3Comments

parmjitv picture parmjitv  路  4Comments